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
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestConnectorMultiplicityBoundsLinks(t *testing.T) {
t.Fatalf("%s = %v, want what its own declaration states", expr, err)
}
if got := len(elementsOf(val)); got != want {
t.Errorf("%s = %s, want %d object(s) of its own", expr, FormatValue(val), want)
t.Errorf("%s = %s holds %d object(s), want %d of its own", expr, FormatValue(val), got, want)
}
}
// `many : Real [2]` states no value, so the binding to `own` leaves it undetermined.
Expand Down
2 changes: 1 addition & 1 deletion internal/exec/runtime/calc_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ func (ctx *Context) bindCalcUsage(shape *calcShape, reader *EvalContext, args ca
if shape.readsOpen(nested, args) {
open = unsupplied
}
if err := ctx.bindCalcParameters(shape, ec, args, reader.scope, env, nested, occurrence, open); err != nil {
if err := ctx.bindCalcParameters(shape, ec, args, reader.scope, env, calcBindingSite{nested: nested, occurrence: occurrence, open: open}); err != nil {
if ec.trace != nil {
ec.trace.recordCalculationExitError(shape.Kind, shape.Name, err)
}
Expand Down
17 changes: 12 additions & 5 deletions internal/exec/runtime/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func (ctx *Context) materializeConnectorCollection(owner *Instance, fv *FeatureV
mark := len(ctx.created)
commit, rollback := ctx.beginJournal()
endBoundary := ctx.beginRunBoundary()
undoFills := func() {}
filled, undoFills, err := ctx.fillOptionalConnectorSubsetters(owner, name, int(count))
fail := func(err error) error {
fv.Values, fv.Materialized = Value{}, false
undoFills()
Expand All @@ -93,12 +93,10 @@ func (ctx *Context) materializeConnectorCollection(owner *Instance, fv *FeatureV
release()
return err
}
elements := append(make([]Value, 0, int64(len(contributed))+count), contributed...)
filled, undo, err := ctx.fillOptionalConnectorSubsetters(owner, name, int(count))
undoFills = undo
if err != nil {
return fail(err)
}
elements := append(make([]Value, 0, int64(len(contributed))+count), contributed...)
conns := append(make([]*Instance, 0, count), filled...)
for _, conn := range filled {
elements = append(elements, Value{Kind: ValInstance, Instance: conn.ID})
Expand Down Expand Up @@ -359,13 +357,22 @@ func (ctx *Context) materializeConnectorStarting(owner *Instance, connSym, base
return ctx.runAttachedBehaviors()
}

// noJournal stands for a journal nothing opened: committing or rolling back has nothing to
// make durable or undo, and no behavior was woken that a drain could let answer.
func noJournal() (commit, rollback func(), drain func() error) {
nothing := func() {
// Nothing was opened, so there is nothing to commit or undo.
}
return nothing, nothing, func() error { return nil }
}

// connectorJournal opens a journal and a run boundary when sym is a connector usage, so what a
// namespace path does for it — attach, record, start — is undone whole on failure as a nested
// connector's is. commit makes it durable; drain, once the path's own guards are cleared, lets the
// older behaviors it woke answer, a failure of theirs their own. For any other usage all are no-ops.
func (ctx *Context) connectorJournal(sym *symbols.Symbol) (commit, rollback func(), drain func() error) {
if !ctx.model.semantics.IsConnectorObjectUsage(sym) {
return func() {}, func() {}, func() error { return nil }
return noJournal()
}
endBoundary := ctx.beginRunBoundary()
commitJournal, rollbackJournal := ctx.beginJournal()
Expand Down
110 changes: 69 additions & 41 deletions internal/exec/runtime/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -1616,14 +1616,8 @@ func (ec *EvalContext) evalFeatureChain(n *ast.FeatureChainExpr) (Value, error)

// A constraint usage of the object being evaluated is the predicate it enacts,
// read through the object (`fits.limit` as `self.fits.limit`).
if name := simpleEndName(base); name != "" && ec.self != nil {
if _, enacted := ec.enactedConstraint(ec.self, name); enacted {
if names, err := ec.namesValue(name); err != nil {
return Value{}, err
} else if !names {
return ec.chainMemberValue(Value{Kind: ValInstance, Instance: ec.self.ID}, append([]ast.NameSegment{{Text: name}}, parts...), "")
}
}
if val, ok, err := ec.enactedConstraintChain(base, parts); ok {
return val, err
}

// A variant named through the variation usage it belongs to is that variant,
Expand All @@ -1639,48 +1633,83 @@ func (ec *EvalContext) evalFeatureChain(n *ast.FeatureChainExpr) (Value, error)
if sym, ok, err := ec.occurrenceOperand(base); err != nil {
return Value{}, err
} else if ok {
// A usage of the behavior performed is read from its performance, so `fresh.n`
// inside `go` reads the part the performance holds, as a bare `fresh` does.
if val, ok, err := ec.performanceFeatureOf(sym); ok {
if err != nil {
return Value{}, err
}
return ec.chainMemberValue(val, parts, sym.Name)
}
// A usage of an enclosing object is read from that object, so a sibling
// chain `e1.length` inside `e3` reads the containing rectangle's e1.
if val, ok, err := ec.outerFeatureValue(sym); ok {
if err != nil {
return Value{}, err
}
return ec.chainMemberValue(val, parts, sym.Name)
}
// The objects read as their values, whose own members they answer before the objects'.
val, err := ec.ctx.denotedValue(sym)
if err != nil {
return Value{}, err
}
return ec.chainMemberValue(val, parts, sym.Name)
return ec.occurrenceChainValue(sym, parts)
}

// One chain is one read from its first operand through every member in turn
// (KerML 1.0 §7.4.9.3), not a read off the value of a shorter chain.
operand, err := ec.Eval(base)
if err != nil {
var noValue *NoValueError
if errors.As(err, &noValue) {
if unresolved := ec.chainMembersDeclared(base, parts); unresolved != nil {
return Value{}, unresolved
}
// A usage no object stands for answers the chain through the
// value its member declares, as `usage::member` reads it.
if val, ok, err := ec.declaredChainValue(base, parts); ok {
return val, err
}
return ec.valuelessChain(err, base, parts)
}
return ec.operandChainValue(operand, base, parts)
}

// enactedConstraintChain reads a chain whose base names a constraint usage the
// object being evaluated enacts through that object, unless the name has a value.
func (ec *EvalContext) enactedConstraintChain(base ast.Node, parts []ast.NameSegment) (Value, bool, error) {
name := simpleEndName(base)
if name == "" || ec.self == nil {
return Value{}, false, nil
}
if _, enacted := ec.enactedConstraint(ec.self, name); !enacted {
return Value{}, false, nil
}
if names, err := ec.namesValue(name); err != nil {
return Value{}, true, err
} else if names {
return Value{}, false, nil
}
val, err := ec.chainMemberValue(Value{Kind: ValInstance, Instance: ec.self.ID}, append([]ast.NameSegment{{Text: name}}, parts...), "")
return val, true, err
}

// occurrenceChainValue reads the chain's members from the object sym denotes.
func (ec *EvalContext) occurrenceChainValue(sym *symbols.Symbol, parts []ast.NameSegment) (Value, error) {
// A usage of the behavior performed is read from its performance, so `fresh.n`
// inside `go` reads the part the performance holds, as a bare `fresh` does.
if val, ok, err := ec.performanceFeatureOf(sym); ok {
if err != nil {
return Value{}, err
}
return ec.chainMemberValue(val, parts, sym.Name)
}
// A usage of an enclosing object is read from that object, so a sibling
// chain `e1.length` inside `e3` reads the containing rectangle's e1.
if val, ok, err := ec.outerFeatureValue(sym); ok {
if err != nil {
return Value{}, err
}
return ec.chainMemberValue(val, parts, sym.Name)
}
// The objects read as their values, whose own members they answer before the objects'.
val, err := ec.ctx.denotedValue(sym)
if err != nil {
return Value{}, err
}
return ec.chainMemberValue(val, parts, sym.Name)
}

// valuelessChain answers a chain whose base has no value: unresolved when a
// member is undeclared, the declared member value when there is one, else err.
func (ec *EvalContext) valuelessChain(err error, base ast.Node, parts []ast.NameSegment) (Value, error) {
var noValue *NoValueError
if !errors.As(err, &noValue) {
return Value{}, err
}
if unresolved := ec.chainMembersDeclared(base, parts); unresolved != nil {
return Value{}, unresolved
}
// A usage no object stands for answers the chain through the
// value its member declares, as `usage::member` reads it.
if val, ok, err := ec.declaredChainValue(base, parts); ok {
return val, err
}
return Value{}, err
}

// operandChainValue reads the chain's members from the value its base evaluated to.
func (ec *EvalContext) operandChainValue(operand Value, base ast.Node, parts []ast.NameSegment) (Value, error) {
if operand.Kind == ValInstance {
if _, ok := ec.ctx.instances[operand.Instance]; !ok {
return Value{}, fmt.Errorf("instance ID %d not found", operand.Instance)
Expand All @@ -1700,7 +1729,6 @@ func (ec *EvalContext) evalFeatureChain(n *ast.FeatureChainExpr) (Value, error)
return val, err
}
}

return ec.chainMemberValue(operand, parts, "")
}

Expand Down
110 changes: 64 additions & 46 deletions internal/exec/runtime/held_image.go
Original file line number Diff line number Diff line change
Expand Up @@ -1089,70 +1089,88 @@ func (m *materializing) message(msg Message) (Message, error) {
// recordedOccurrences brings the image's shots, fired successions and transfers
// to dst under the identities the objects were made with.
func (m *materializing) recordedOccurrences() error {
made := func(id int64) int64 {
if inst, ok := m.made[id]; ok {
return inst.ID
}
return 0
}
madeAll := func(ids []int64) []int64 {
out := make([]int64, 0, len(ids))
for _, id := range ids {
if to := made(id); to != 0 {
out = append(out, to)
}
}
return out
}
for id, values := range m.img.startValues {
to := made(id)
to := m.madeID(id)
if to == 0 {
continue
}
carried := make(map[string]Value, len(values))
for name, value := range values {
value, err := m.value(value)
if err != nil {
return fmt.Errorf("object #%d start value %s: %w", id, name, err)
}
carried[name] = value
carried, err := m.carriedStartValues(id, values)
if err != nil {
return err
}
m.dst.startValues[to] = carried
}
for id, shots := range m.img.shots {
if to := made(id); to != 0 {
m.dst.shots[to] = occurrenceShots{start: made(shots.start), end: made(shots.end)}
if to := m.madeID(id); to != 0 {
m.dst.shots[to] = occurrenceShots{start: m.madeID(shots.start), end: m.madeID(shots.end)}
}
}
for id, predecessors := range m.img.fired {
if to := made(id); to != 0 {
m.dst.fired[to] = madeAll(predecessors)
if to := m.madeID(id); to != 0 {
m.dst.fired[to] = m.madeIDs(predecessors)
}
}
for _, record := range m.img.transfers {
to := made(record.id)
to := m.madeID(record.id)
if to == 0 || slices.ContainsFunc(m.dst.transfers, func(r transferRecord) bool { return r.id == to }) {
continue
}
payload := record.payload
if elems := elementsOf(payload); len(elems) > 0 {
mapped := make([]Value, 0, len(elems))
for _, elem := range elems {
if elem.Kind == ValInstance {
elem = Value{Kind: ValInstance, Instance: made(elem.Instance)}
}
mapped = append(mapped, elem)
}
if payload.Kind == ValInstance {
payload = mapped[0]
} else {
payload = sequenceOf(mapped)
m.dst.transfers = append(m.dst.transfers, m.carriedTransfer(to, record))
}
return nil
}

// madeID is the identity the image's object id was made under, 0 when it was not made.
func (m *materializing) madeID(id int64) int64 {
if inst, ok := m.made[id]; ok {
return inst.ID
}
return 0
}

// madeIDs maps ids to the identities they were made under, dropping those not made.
func (m *materializing) madeIDs(ids []int64) []int64 {
out := make([]int64, 0, len(ids))
for _, id := range ids {
if to := m.madeID(id); to != 0 {
out = append(out, to)
}
}
return out
}

// carriedStartValues brings the start values recorded for the image's object id to dst.
func (m *materializing) carriedStartValues(id int64, values map[string]Value) (map[string]Value, error) {
carried := make(map[string]Value, len(values))
for name, value := range values {
value, err := m.value(value)
if err != nil {
return nil, fmt.Errorf("object #%d start value %s: %w", id, name, err)
}
carried[name] = value
}
return carried, nil
}

// carriedTransfer is record for the transfer made as to, its ends and payload mapped alike.
func (m *materializing) carriedTransfer(to int64, record transferRecord) transferRecord {
payload := record.payload
if elems := elementsOf(payload); len(elems) > 0 {
mapped := make([]Value, 0, len(elems))
for _, elem := range elems {
if elem.Kind == ValInstance {
elem = Value{Kind: ValInstance, Instance: m.madeID(elem.Instance)}
}
mapped = append(mapped, elem)
}
if payload.Kind == ValInstance {
payload = mapped[0]
} else {
payload = sequenceOf(mapped)
}
m.dst.transfers = append(m.dst.transfers, transferRecord{
id: to, source: made(record.source), target: made(record.target), payload: payload,
sources: madeAll(record.sources), targets: madeAll(record.targets),
})
}
return nil
return transferRecord{
id: to, source: m.madeID(record.source), target: m.madeID(record.target), payload: payload,
sources: m.madeIDs(record.sources), targets: m.madeIDs(record.targets),
}
}
22 changes: 13 additions & 9 deletions internal/exec/runtime/invoke_calc.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ func (ctx *Context) invokeCalcShapeDirect(shape *calcShape, args calcArgs, calle
ec.trace.recordCalculationEnter(shape.Kind, shape.Name)
}

if err := ctx.bindCalcParameters(shape, ec, args, callerScope, locals, nil, occurrence, nil); err != nil {
if err := ctx.bindCalcParameters(shape, ec, args, callerScope, locals, calcBindingSite{occurrence: occurrence}); err != nil {
if ec.trace != nil {
ec.trace.recordCalculationExitError(shape.Kind, shape.Name, err)
}
Expand Down Expand Up @@ -923,23 +923,27 @@ func (ctx *Context) leaveCalc() {
ctx.calcDepth--
}

// calcBindingSite is where a usage's bindings land beyond the call: the nested
// reader they evaluate in, the performance written to, and the open parameters.
type calcBindingSite struct {
nested *EvalContext
occurrence *calcOccurrence
open map[string]bool
}

// bindCalcParameters binds the calc's input parameters into bindings: each
// parameter's argument, or, for a parameter no argument supplies, the default
// declared closest to the invoked calc, evaluated in the scope of the calc
// declaring it. ec supplies the environment defaults are evaluated in; nested,
// when non-null, is the environment reading a nested usage, which the bindings
// the usage itself declares are written in and evaluate against. open names the
// parameters a usage read as a feature leaves unsupplied, bound to no value.
// declaring it. ec supplies the environment defaults are evaluated in.
func (ctx *Context) bindCalcParameters(
shape *calcShape,
ec *EvalContext,
args calcArgs,
callerScope *symbols.Scope,
bindings frame,
nested *EvalContext,
occurrence *calcOccurrence,
open map[string]bool,
site calcBindingSite,
) error {
nested, occurrence, open := site.nested, site.occurrence, site.open
positional := 0
for i := range shape.Params {
param := &shape.Params[i]
Expand Down Expand Up @@ -1482,7 +1486,7 @@ func (ctx *Context) applyLibraryPerformance(perf *libraryPerformance, args calcA
trace: ctx.trace,
activation: activation,
}
if err := ctx.bindCalcParameters(sig, ec, args, callerScope, locals, nil, nil, nil); err != nil {
if err := ctx.bindCalcParameters(sig, ec, args, callerScope, locals, calcBindingSite{}); err != nil {
return Value{}, err
}

Expand Down
Loading
Loading