Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/unreleased/namespace-read-multiplicity.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- A feature read at namespace level (`sysml -e P::xs`, a bare expression or `%eval` in the REPL, gRPC `Evaluate`) now holds its value in the feature's effective multiplicity, as the same feature read on an object already did: a `[0..*]` attribute valued `= 4` reads `[4]` on both, where the model-level read printed the bare `4`. An at-most-one feature (`[1]`, `[0..1]`, a bare `attribute x : Integer`) still reads its one value bare, and arithmetic and comparison over the read (`P::xs + 1`, `P::xs == 4`) are unchanged. A parameter stating no multiplicity is held to `[0..*]`, so its one default reads as a one-element sequence too, and a call of a calc-typed parameter applies the one function it holds.
2 changes: 1 addition & 1 deletion docs/project/spec-compliance.md

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion internal/exec/runtime/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -1333,7 +1333,11 @@ func (ec *EvalContext) conformHeld(sym *symbols.Symbol, val Value, countJudged b
if err := ec.ctx.classifyHeld(sym, val); err != nil {
return Value{}, fmt.Errorf("%s: %w", what(), err)
}
return ec.bindVariationOf(sym, ec.ctx.classifiedFrame(sym, ec.ctx.declaredCollection(sym, val)))
val, err := ec.ctx.declaredCollection(sym, val)
if err != nil {
return Value{}, fmt.Errorf("%s: %w", what(), err)
}
return ec.bindVariationOf(sym, ec.ctx.classifiedFrame(sym, val))
}

// occurrenceReference evaluates a name denoting objects of its own — an occurrence, a
Expand Down
7 changes: 4 additions & 3 deletions internal/exec/runtime/function_value.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,12 @@ func (ec *EvalContext) boundFunction(callee *symbols.Symbol, qn *ast.QualifiedNa
return val, true, nil
}
// A calc-typed feature the element being evaluated binds (`in calc :>> f = g`)
// is called as the function it is bound to.
// is called as the function it is bound to: a call takes the one function it holds.
if val, ok, err := ec.valuedFeatureValue(name); ok {
if err != nil {
return Value{}, true, err
}
if val.Kind == ValFunction {
if val = soleElement(val); val.Kind == ValFunction {
return val, true, nil
}
}
Expand All @@ -196,7 +196,7 @@ func (ec *EvalContext) boundFunction(callee *symbols.Symbol, qn *ast.QualifiedNa
if err != nil {
return Value{}, true, err
}
if ok && val.Kind == ValFunction {
if val = soleElement(val); ok && val.Kind == ValFunction {
return val, true, nil
}
}
Expand All @@ -218,6 +218,7 @@ func (ec *EvalContext) declaredFunction(callee *symbols.Symbol) (Value, bool, er
if err != nil {
return Value{}, true, err
}
val = soleElement(val)
return val, val.Kind == ValFunction, nil
}

Expand Down
32 changes: 21 additions & 11 deletions internal/exec/runtime/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,7 @@ func (ctx *Context) denotedValue(sym *symbols.Symbol) (Value, error) {
if tail != nil {
return NewSequenceValue(&Sequence{elements: elements, required: tail}), nil
}
return ctx.declaredCollection(sym, sequenceOf(elements)), nil
return ctx.declaredCollection(sym, sequenceOf(elements))
}

// liveOccurrences is the objects recorded as what sym denotes, while every one of them lives.
Expand Down Expand Up @@ -857,24 +857,34 @@ func (feat *EffectiveFeature) heldBy() *symbols.Symbol {
return feat.Type
}

// admitted is the value an admitted val is stored as: a collection for a multi-valued
// feature, its elements charged, the objects a declared value holds classified by the feature.
func (ctx *Context) admitted(feat *EffectiveFeature, val Value, how admission) (Value, error) {
// heldShape is val in the shape feat holds it (KerML 1.0 §7.3.4.1, §7.4.12): a multi-valued
// feature holds a charged collection, one value its one element; an at-most-one feature holds it bare.
func (ctx *Context) heldShape(feat *EffectiveFeature, val Value) (Value, error) {
if val.Kind == ValUndetermined {
// A value the model leaves open is held as it is: it is of no fixed count to collect.
return val, nil
}
if !feat.Scalar() && (val.Kind != ValSequence && val.Kind != ValSet || feat.HoldsSet != (val.Kind == ValSet)) {
// A multi-valued feature holds a collection however it was written, so a
// single value written to one is that collection's one element.
if feat.Scalar() {
return soleElement(val), nil
}
if val.Kind != ValSequence && val.Kind != ValSet || feat.HoldsSet != (val.Kind == ValSet) {
elements := elementsOf(val)
if err := ctx.chargeElements(int64(len(elements))); err != nil {
return Value{}, err
}
val = ctx.collectionOf(feat, elements)
} else if feat.Scalar() {
val = soleElement(val)
} else if err := ctx.chargeShaped(val); err != nil {
return ctx.collectionOf(feat, elements), nil
}
if err := ctx.chargeShaped(val); err != nil {
return Value{}, err
}
return val, nil
}

// admitted is the value an admitted val is stored as: a collection for a multi-valued
// feature, its elements charged, the objects a declared value holds classified by the feature.
func (ctx *Context) admitted(feat *EffectiveFeature, val Value, how admission) (Value, error) {
val, err := ctx.heldShape(feat, val)
if err != nil {
return Value{}, err
}
if how == admitDeclared {
Expand Down
5 changes: 3 additions & 2 deletions internal/exec/runtime/robustness_usage_members_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ func testUsageMembersUnboundConstraintInput(t *testing.T) {
if err != nil || val.Kind != ValUndetermined {
t.Fatalf("Probe::rig.under.limit = %s, %v; want undetermined for the input nothing binds", FormatValue(val), err)
}
if val, err := eval("Probe::rig.fits.limit"); err != nil || FormatValue(val) != "100.0" {
t.Fatalf("Probe::rig.fits.limit = %s, %v; want the default 100.0", FormatValue(val), err)
// A parameter stating no multiplicity is held to [0..*], so its one default is a one-element sequence.
if val, err := eval("Probe::rig.fits.limit"); err != nil || FormatValue(val) != "[100.0]" {
t.Fatalf("Probe::rig.fits.limit = %s, %v; want the default held as [100.0]", FormatValue(val), err)
}
if val, err := eval("Probe::rig.fits"); err != nil || FormatValue(val) != "true" {
t.Fatalf("Probe::rig.fits = %s, %v; want the verdict", FormatValue(val), err)
Expand Down
20 changes: 5 additions & 15 deletions internal/exec/runtime/set_feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,10 @@ func (ctx *Context) collectionOf(feat *EffectiveFeature, elements []Value) Value
return sequenceOf(elements)
}

// declaredCollection is a collection read through a declaration, in the shape that
// declaration holds: one value held by an at-most-one multiplicity is held bare; a set
// flowing into a sequence is its canonical sequence, a sequence flowing into a set its
// distinct elements.
func (ctx *Context) declaredCollection(sym *symbols.Symbol, val Value) Value {
if val.Kind != ValSet && val.Kind != ValSequence {
return val
}
// declaredCollection is a value read through a declaration, in the shape that declaration
// holds it (heldShape), so a `[0..*]` feature valued `4` reads `[4]` at namespace level too.
func (ctx *Context) declaredCollection(sym *symbols.Symbol, val Value) (Value, error) {
mult := ctx.featureMultiplicity(sym, nil)
if !mult.Upper.Infinite && mult.Upper.Value <= 1 {
return soleElement(val)
}
if holds := ctx.holdsSet(sym, ctx.findOwnerType(sym), mult); holds != (val.Kind == ValSet) {
return ctx.collectionOf(&EffectiveFeature{HoldsSet: holds}, elementsOf(val))
}
return val
feat := &EffectiveFeature{Symbol: sym, Multiplicity: mult, HoldsSet: ctx.holdsSet(sym, ctx.findOwnerType(sym), mult)}
return ctx.heldShape(feat, val)
}
62 changes: 62 additions & 0 deletions internal/frontend/repl/namespace_shape_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package repl

import (
"strings"
"testing"
)

// A namespace-level read holds its value in the feature's effective multiplicity as the
// same feature read on an object does: `[0..*]` valued `4` is `[4]` on both, at-most-one bare.
func TestEvalNamespaceReadHoldsEffectiveMultiplicity(t *testing.T) {
s := NewSession()
res := s.Submit(`package P {
private import ScalarValues::*;
attribute xs : Integer[0..*] = 4;
attribute ys : Integer[0..*] = (1, 2);
attribute e : Integer[0..*];
attribute one : Integer[1] = 4;
attribute opt : Integer[0..1] = 4;
attribute bare : Integer = 4;
attribute two : Integer[2] = (4, 5);
attribute seq : Integer[0..*] = (4);
part def D {
attribute xs : Integer[0..*] = 4;
attribute e : Integer[0..*];
attribute one : Integer[1] = 4;
attribute opt : Integer[0..1] = 4;
attribute bare : Integer = 4;
attribute two : Integer[2] = (4, 5);
attribute seq : Integer[0..*] = (4);
}
part d : D;
}`)
if len(res.Diagnostics) > 0 {
t.Fatalf("fixture has diagnostics: %v", res.Diagnostics)
}
for expr, want := range map[string]string{
"P::xs": "= [4]",
"P::d.xs": "= [4]",
"P::ys": "= [1, 2]",
"P::e": "= []",
"P::d.e": "= []",
"P::one": "= 4",
"P::d.one": "= 4",
"P::opt": "= 4",
"P::d.opt": "= 4",
"P::bare": "= 4",
"P::d.bare": "= 4",
"P::two": "= [4, 5]",
"P::d.two": "= [4, 5]",
"P::seq": "= [4]",
"P::d.seq": "= [4]",
"P::xs + 1": "= 5",
"P::xs == 4": "= true",
} {
wants(t, run(t, s, "%eval "+expr), want)
lines, err := s.EvalBare(expr)
if err != nil {
t.Fatalf("%s: %v", expr, err)
}
wants(t, strings.Join(lines, "\n"), want)
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package P {
private import ScalarValues::*;
attribute xs : Integer[0..*] = 4;
part def D { attribute xs : Integer[0..*] = 4; }
part d : D;
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package P {
private import ScalarValues::*;
attribute xs : Integer[0..*] = 4;
part def D { attribute xs : Integer[0..*] = 4; }
part d : D;
}
Loading