Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
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/origin-node-descriptions.changed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- A run's diagnostics name an unnamed action node by its construct — `the if action`, `the decision node`, `the while loop`, `the fork node`, `the statement` — where they printed the syntax tree's type name (`the *ast.IfActionNode`) before.
35 changes: 25 additions & 10 deletions internal/doc/queryexec/states.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/Open-MBEE/OpenSysML/internal/exec/runtime"
"github.com/Open-MBEE/OpenSysML/internal/ir/lower"
"github.com/Open-MBEE/OpenSysML/internal/ir/program"
"github.com/Open-MBEE/OpenSysML/internal/ir/queryplan"
"github.com/Open-MBEE/OpenSysML/internal/semantic/query"
"github.com/Open-MBEE/OpenSysML/internal/semantic/symbols"
Expand Down Expand Up @@ -206,32 +207,40 @@ func stateMachinesOf(inst *runtime.Instance) []*runtime.ObjectBehavior {
return out
}

// activeState describes one active leaf of a machine as a row.
// activeState describes one active leaf of a machine as a row, by the origins
// the graph lowered for the leaf and the states enclosing it.
func activeState(inst *runtime.Instance, label string, machine *runtime.ObjectBehavior, leaf *ast.StateNode) State {
exec := machine.State
graph := exec.Graph()
enclosing := exec.EnclosingStates(leaf)
names := make([]string, 0, len(enclosing))
for _, s := range enclosing {
names = append(names, s.Name)
names = append(names, stateName(graph, s))
}
return State{
object: inst,
label: label,
machine: machineName(machine),
symbol: stateSymbol(exec.Graph(), leaf),
name: leaf.Name,
symbol: stateSymbol(graph, graph.ID[leaf]),
name: stateName(graph, leaf),
path: exec.StatePath(leaf),
region: regionName(exec, leaf),
enclosing: names,
behavior: machine.Symbol,
}
}

// stateName is a state's name as its origin records it.
func stateName(graph *lower.StateGraph, state *ast.StateNode) string {
return graph.Origins[graph.ID[state]].Name
}

// regionName names the innermost orthogonal region state stands in, its own or an
// ancestor's, "" for a state outside every region.
func regionName(exec *runtime.StateExecutor, state *ast.StateNode) string {
if region := exec.RegionOf(state); region != nil {
return region.Name
graph := exec.Graph()
return graph.RegionOrigins[graph.RegionID[region]].Name
}
return ""
}
Expand All @@ -248,9 +257,13 @@ func machineName(machine *runtime.ObjectBehavior) string {
return ""
}

// stateSymbol is the symbol declaring a state of the graph, nil for one
// lowering synthesized without a declaration.
func stateSymbol(graph *lower.StateGraph, state *ast.StateNode) *symbols.Symbol {
// stateSymbol is the symbol declaring the state with id in the graph, found
// through the graph's provenance; nil for one lowering synthesized without a declaration.
func stateSymbol(graph *lower.StateGraph, id program.StateID) *symbols.Symbol {
state := graph.Provenance.State(id)
if state == nil {
return nil
}
decl := graph.DeclOf(state)
scope := graph.StateScopes[state]
if scope == nil {
Expand All @@ -270,11 +283,13 @@ func stateSymbol(graph *lower.StateGraph, state *ast.StateNode) *symbols.Symbol
// stateNamed reports whether name denotes leaf or a state enclosing it, by
// the state's own name or by its path (`on.run`).
func stateNamed(exec *runtime.StateExecutor, leaf *ast.StateNode, name string) bool {
graph := exec.Graph()
chain := append(exec.EnclosingStates(leaf), leaf)
parts := make([]string, 0, len(chain))
for _, state := range chain {
parts = append(parts, state.Name)
if state.Name == name || strings.Join(parts, ".") == name {
own := stateName(graph, state)
parts = append(parts, own)
if own == name || strings.Join(parts, ".") == name {
return true
}
}
Expand Down
10 changes: 5 additions & 5 deletions internal/exec/runtime/action_control_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (e *performances) carryObjects(frame *actionFrame, node ast.Node, via lower
ec.pushFrame(mapFrame(inputs))
value, err := ec.Eval(f.Value)
if err != nil {
return fmt.Errorf("%s: %s: %w", nodeDescription(node), f.Name, err)
return fmt.Errorf("%s: %s: %w", describeNode(graph, node), f.Name, err)
}
if len(elementsOf(value)) > 0 {
produced[f.Name] = soleElement(value)
Expand Down Expand Up @@ -111,7 +111,7 @@ func arrivingObject(frame *actionFrame, graph *lower.ActionGraph, node ast.Node,
case len(held) == 1:
return held[0], first[held[0]], nil
case len(held) > 1:
return "", -1, fmt.Errorf("%w: %s holds values at %s", errAmbiguousMergeInput, nodeDescription(node), strings.Join(held, ", "))
return "", -1, fmt.Errorf("%w: %s holds values at %s", errAmbiguousMergeInput, describeNode(graph, node), strings.Join(held, ", "))
case len(from) > 0 || via.Carries:
return "", -1, nil
}
Expand All @@ -126,22 +126,22 @@ func arrivingObject(frame *actionFrame, graph *lower.ActionGraph, node ast.Node,
case 1:
return held[0], slices.IndexFunc(frame.held[refOf(graph, node)][held[0]], func(h nodeObject) bool { return !h.carried }), nil
}
return "", -1, fmt.Errorf("%w: %s holds values at %s", errAmbiguousMergeInput, nodeDescription(node), strings.Join(held, ", "))
return "", -1, fmt.Errorf("%w: %s holds values at %s", errAmbiguousMergeInput, describeNode(graph, node), strings.Join(held, ", "))
}

// queueControlObject holds a value an object flow delivers at an input of a fork, join
// or merge until a token passes the node.
func (e *performances) queueControlObject(frame *actionFrame, graph *lower.ActionGraph, flow lower.ObjectFlow, value Value) error {
for _, f := range graph.Features[flow.Target] {
if f.Name == flow.TargetPin && f.Direction == ast.DirIn {
if err := e.ctx.checkNamedWriteAs(graph.Scopes[flow.Target], func() string { return nodeDescription(flow.Target) }, f.Name, &value); err != nil {
if err := e.ctx.checkNamedWriteAs(graph.Scopes[flow.Target], func() string { return describeNode(graph, flow.Target) }, f.Name, &value); err != nil {
return fmt.Errorf("%s: %w", flowDescription(flow), err)
}
frame.hold(refOf(graph, flow.Target), f.Name, nodeObject{value: value, flow: flow.Decl, carried: flow.Kind == lower.FlowSuccession})
return nil
}
}
return fmt.Errorf("%s: %w: %s declares no input %s", flowDescription(flow), errNodePin, nodeDescription(flow.Target), flow.TargetPin)
return fmt.Errorf("%s: %w: %s declares no input %s", flowDescription(flow), errNodePin, describeNode(graph, flow.Target), flow.TargetPin)
}

// hold appends a value to those node holds at pin.
Expand Down
30 changes: 16 additions & 14 deletions internal/exec/runtime/action_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/Open-MBEE/OpenSysML/internal/ir/lower"
"github.com/Open-MBEE/OpenSysML/internal/ir/program"
"github.com/Open-MBEE/OpenSysML/internal/semantic/resolve"
"github.com/Open-MBEE/OpenSysML/internal/semantic/semantics"
"github.com/Open-MBEE/OpenSysML/internal/semantic/symbols"
Expand Down Expand Up @@ -1343,7 +1344,7 @@ func (e *ActionExecutor) breakpointNameOf(within []NodeRef, node NodeRef) string
if name := ActionNodeName(node.Node()); name != "" {
return name
}
return nodeDescription(node.Node())
return node.Origin().String()
}
for _, name := range actionNodeNames(node.Node()) {
if e.breakpoints[name] {
Expand Down Expand Up @@ -2322,7 +2323,7 @@ func (e *ActionExecutor) enabledSuccessions(frame *actionFrame, node ast.Node) (

enabled := make([]lower.ActionEdge, 0, len(declared))
for _, edge := range declared {
holds, err := e.guardHolds(ec.evalIn(graph.DeclScope(edge.Decl)), node, edge.Guard)
holds, err := e.guardHolds(ec.evalIn(graph.DeclScope(edge.Decl)), graph, node, edge.Guard)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -2358,18 +2359,18 @@ func (e *ActionExecutor) falseGuardLeavesRepeated(graph *lower.ActionGraph, edge

// guardHolds evaluates the guard a succession out of node carries; none is unconditional, and
// one reading a feature with no value is not true, so it does not assert it (SysML v2 7.17.5).
func (e *ActionExecutor) guardHolds(ec *EvalContext, node, guard ast.Node) (bool, error) {
func (e *ActionExecutor) guardHolds(ec *EvalContext, graph *lower.ActionGraph, node, guard ast.Node) (bool, error) {
return e.ctx.guardUnderStatementOrders(guard, e.stepCount+1, ec.scope, func() (bool, error) {
result, err := guardResult(ec, guard)
if readsNoValue(err) {
return false, nil
}
if err != nil {
return false, fmt.Errorf("eval guard of %s: %w", nodeDescription(node), err)
return false, fmt.Errorf("eval guard of %s: %w", describeNode(graph, node), err)
}
if !result.isBool() {
return false, fmt.Errorf("%w: %s: guard must evaluate to boolean, got %v",
ErrTypeMismatch, nodeDescription(node), result.Kind)
ErrTypeMismatch, describeNode(graph, node), result.Kind)
}
return result.Const.Bool, nil
})
Expand Down Expand Up @@ -2822,7 +2823,7 @@ func (e *ActionExecutor) stepDecisionNode(tokenIdx int) error {
}
} else {
var err error
if holds, err = e.guardHolds(ec.evalIn(graph.DeclScope(edge.Decl)), decisionNode, edge.Guard); err != nil {
if holds, err = e.guardHolds(ec.evalIn(graph.DeclScope(edge.Decl)), graph, decisionNode, edge.Guard); err != nil {
return err
}
}
Expand All @@ -2843,7 +2844,7 @@ func (e *ActionExecutor) stepDecisionNode(tokenIdx int) error {
// is the run's own, so the run holds what evaluating it did.
if pick > 0 && successors[holding[pick]].Guard != nil {
picked := successors[holding[pick]]
holds, err := e.guardHolds(ec.evalIn(graph.DeclScope(picked.Decl)), decisionNode, picked.Guard)
holds, err := e.guardHolds(ec.evalIn(graph.DeclScope(picked.Decl)), graph, decisionNode, picked.Guard)
if err != nil {
return err
}
Expand Down Expand Up @@ -3508,7 +3509,7 @@ func (e *performances) applyDataFlows(
}
return fmt.Errorf(
"%w: %s: %s produced no value at %s",
errFlowSource, flowDescription(flow), nodeDescription(sourceNode), orAnyPin(flow.SourcePin),
errFlowSource, flowDescription(flow), describeNode(graph, sourceNode), orAnyPin(flow.SourcePin),
)
}
var err error
Expand Down Expand Up @@ -3572,13 +3573,14 @@ func orAnyPin(pin string) string {
return pin
}

// nodeDescription names an action node for a diagnostic, falling back to the
// kind of node it is when the notation gave it no name.
func nodeDescription(node ast.Node) string {
if name := ActionNodeName(node); name != "" {
return "node " + name
//go:noinline
func describeNode(graph *lower.ActionGraph, node ast.Node) string {
// Names a node for a diagnostic by its origin (`node a`, else `the if action`); kept out
// of line because inlined into every error path it cost 17 KB of the wasm size budget.
if graph == nil {
return program.Origin{}.String()
}
return fmt.Sprintf("the %T", node)
return graph.OriginOf(node).String()
}

// --- Public accessor methods for REPL debugging ---
Expand Down
26 changes: 13 additions & 13 deletions internal/exec/runtime/action_frame.go
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,7 @@ func (e *performances) seedDeclaredValues(perf *actionFrame, features []lower.Fe
}
}
if err != nil {
return fmt.Errorf("eval %s of %s: %w", feature.Name, nodeDescription(perf.node), err)
return fmt.Errorf("eval %s of %s: %w", feature.Name, describeNode(perf.flow, perf.node), err)
}
if err := e.streamFrom(perf, perf.key(feature.Name), value); err != nil {
return err
Expand Down Expand Up @@ -805,8 +805,8 @@ func checkStreamsReceived(frame *actionFrame) error {
}
stream := frame.unreceived[nodes[0]][0]
return fmt.Errorf("%w: %s: %s completed before %s wrote %s",
errStreamUnreceived, flowDescription(stream.flow), nodeDescription(nodes[0].Node()),
nodeDescription(stream.source), orAnyPin(stream.flow.SourcePin))
errStreamUnreceived, flowDescription(stream.flow), nodes[0].Origin(),
describeNode(nodes[0].Graph, stream.source), orAnyPin(stream.flow.SourcePin))
}

// nodePins are the pins a performance of node holds (its own and its action's),
Expand Down Expand Up @@ -1189,9 +1189,9 @@ func (e *performances) deliver(f *actionFrame, flow *lower.ActionGraph, node ast
return err
}
if !pins.declares(pin) {
return fmt.Errorf("%w: %s declares no %s", errNodePin, nodeDescription(node), pin)
return fmt.Errorf("%w: %s declares no %s", errNodePin, describeNode(flow, node), pin)
}
if err := e.ctx.checkNamedWriteAs(flow.Scopes[node], func() string { return nodeDescription(node) }, pin, &value); err != nil {
if err := e.ctx.checkNamedWriteAs(flow.Scopes[node], func() string { return describeNode(flow, node) }, pin, &value); err != nil {
return err
}
f.queue(refOf(flow, node), canonical(pins.aliases, pin), value)
Expand Down Expand Up @@ -1236,20 +1236,20 @@ func (f *actionFrame) stage(node NodeRef, pin string, source *actionFrame, from
// node declaring pin, so a delivery waiting for a performance is known to have somewhere to go.
func (e *performances) checkNestedDelivery(flow *lower.ActionGraph, node ast.Node, path []ast.Node, pin string, value *Value) error {
for _, next := range path {
flow = lower.NestedFlow(flow, node, next)
if flow == nil {
return fmt.Errorf("%w: %s holds no nested action %s", errNodePin, nodeDescription(node), ActionNodeName(next))
nested := lower.NestedFlow(flow, node, next)
if nested == nil {
return fmt.Errorf("%w: %s holds no nested action %s", errNodePin, describeNode(flow, node), ActionNodeName(next))
}
node = next
flow, node = nested, next
}
pins, err := e.nodePins(flow, node)
if err != nil {
return err
}
if !pins.declares(pin) {
return fmt.Errorf("%w: %s declares no %s", errNodePin, nodeDescription(node), pin)
return fmt.Errorf("%w: %s declares no %s", errNodePin, describeNode(flow, node), pin)
}
return e.ctx.checkNamedWriteAs(flow.Scopes[node], func() string { return nodeDescription(node) }, pin, value)
return e.ctx.checkNamedWriteAs(flow.Scopes[node], func() string { return describeNode(flow, node) }, pin, value)
}

// takeDeliveries moves the oldest delivery at each pin of node into perf, so that
Expand Down Expand Up @@ -1397,9 +1397,9 @@ func (e *performances) streamFlow(
return fmt.Errorf("%s: %w", flowDescription(flow), err)
}
if !pins.declares(flow.TargetPin) {
return fmt.Errorf("%s: %w: %s declares no %s", flowDescription(flow), errNodePin, nodeDescription(flow.Target), flow.TargetPin)
return fmt.Errorf("%s: %w: %s declares no %s", flowDescription(flow), errNodePin, describeNode(graph, flow.Target), flow.TargetPin)
}
if err := e.ctx.checkNamedWriteAs(graph.Scopes[flow.Target], func() string { return nodeDescription(flow.Target) }, flow.TargetPin, &value); err != nil {
if err := e.ctx.checkNamedWriteAs(graph.Scopes[flow.Target], func() string { return describeNode(graph, flow.Target) }, flow.TargetPin, &value); err != nil {
return fmt.Errorf("%s: %w", flowDescription(flow), err)
}
pin := canonical(pins.aliases, flow.TargetPin)
Expand Down
8 changes: 8 additions & 0 deletions internal/exec/runtime/action_node_ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ func refsOf(graph *lower.ActionGraph, nodes []ast.Node) []NodeRef {
return refs
}

// Origin is where the node the reference names was written; the zero Origin for none.
func (r NodeRef) Origin() program.Origin {
if r.Graph == nil || int(r.ID) >= len(r.Graph.Origins) {
return program.Origin{}
}
return r.Graph.Origins[r.ID]
}

// Node is the declaration the reference names, nil for none.
func (r NodeRef) Node() ast.Node {
if r.Graph == nil {
Expand Down
17 changes: 9 additions & 8 deletions internal/exec/runtime/binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/Open-MBEE/OpenSysML/internal/ir/lower"
"github.com/Open-MBEE/OpenSysML/internal/ir/program"
"github.com/Open-MBEE/OpenSysML/internal/semantic/semantics"
"github.com/Open-MBEE/OpenSysML/internal/semantic/symbols"
"github.com/Open-MBEE/OpenSysML/internal/syntax/ast"
Expand Down Expand Up @@ -438,11 +439,11 @@ func (ctx *Context) unmetBindingCounts(unmet []lower.Binding) error {
return nil
}

// claimBinding marks the binding as the one resolving key, so its own probe of the
// other end is not mistaken for a genuine cycle; the returned func releases the claim.
func (ctx *Context) claimBinding(key featureValueRef, decl *ast.Usage) func() {
// claimBinding marks the binding, by its origin, as the one resolving key, so its own
// probe of the other end is not mistaken for a genuine cycle; the returned func releases the claim.
func (ctx *Context) claimBinding(key featureValueRef, binding program.Origin) func() {
previousOwner, hadOwner := ctx.bindingOwners[key]
ctx.bindingOwners[key] = decl
ctx.bindingOwners[key] = binding
return func() {
if hadOwner {
ctx.bindingOwners[key] = previousOwner
Expand Down Expand Up @@ -687,7 +688,7 @@ func (ctx *Context) sharedValues(a, b Value) int64 {
// an end's feature holds, or one reaching the target across a collection.
func (ctx *Context) partialBinding(owner, targetInst *Instance, target *FeatureValue,
binding lower.Binding, key featureValueRef) (partial bool, err error) {
defer ctx.claimBinding(key, binding.Decl)()
defer ctx.claimBinding(key, binding.Origin)()
for end := range binding.Ends {
endpoint, err := ctx.resolveBindingEndpoint(owner, binding, end)
if err != nil {
Expand Down Expand Up @@ -865,7 +866,7 @@ func bindingEndForPath(binding lower.Binding, path string) int {

func (ctx *Context) attemptBinding(owner, targetInst *Instance, target *FeatureValue,
binding lower.Binding, key featureValueRef, carryingRead endRead) (attempt bindingAttempt) {
defer ctx.claimBinding(key, binding.Decl)()
defer ctx.claimBinding(key, binding.Origin)()
attempt.defaulted = binding.Ends[0].Default || binding.Ends[1].Default

left, err := ctx.resolveBindingEndpoint(owner, binding, 0)
Expand Down Expand Up @@ -915,7 +916,7 @@ func (ctx *Context) attemptBinding(owner, targetInst *Instance, target *FeatureV
}
for _, endpoint := range []bindingEndpoint{left, right} {
for _, loc := range endpoint.locations {
if ctx.genuineBindingCycle(featureValueRef{instance: loc.instance.ID, feature: loc.name}, binding.Decl) {
if ctx.genuineBindingCycle(featureValueRef{instance: loc.instance.ID, feature: loc.name}, binding.Origin) {
attempt.cycle = true
}
}
Expand Down Expand Up @@ -1070,7 +1071,7 @@ func (ctx *Context) resolvingBindingEnd(endpoint bindingEndpoint, carries bool)
return false
}

func (ctx *Context) genuineBindingCycle(ref featureValueRef, binding *ast.Usage) bool {
func (ctx *Context) genuineBindingCycle(ref featureValueRef, binding program.Origin) bool {
return ctx.resolvingBindings[ref] && ctx.bindingOwners[ref] != binding
}

Expand Down
Loading
Loading