Skip to content

API Documentation

Complete API reference for OpenSysML packages.

Overview

OpenSysML is organized into core packages under internal/core/, with frontends in internal/lsp/ and internal/repl/.

Package Organization:

github.com/Open-MBEE/OpenSysML
├── internal/core/          # Core language implementation
│   ├── source/             # Source files and position tracking
│   ├── lexer/              # Tokenization
│   ├── parser/             # Parsing to AST
│   ├── ast/                # Abstract Syntax Tree
│   ├── symbols/            # Symbol tables and scopes
│   ├── resolve/            # Name resolution
│   ├── semantics/          # Type system and semantic queries
│   ├── passes/             # Validation passes
│   ├── lower/              # AST → execution IR (ActionGraph/StateGraph)
│   ├── runtime/            # Execution runtime
│   ├── model/              # Workspace and document management
│   └── libs/               # Standard library handling
├── internal/lsp/           # Language Server Protocol
├── internal/grpc/          # gRPC service implementation
└── internal/repl/          # Interactive REPL

Core Packages

internal/core/source

Source file management and position tracking.

Key Types:

  • SourceFile — Represents a source file with content and line indexing
  • Name() string — File path
  • Content() []byte — Raw bytes
  • LineCount() int — Number of lines
  • Line(n int) string — Get line content

  • Span — Position range in source (offset-based)

  • Start, End int — Byte offsets
  • Contains(pos int) bool
  • Overlaps(other Span) bool

Usage:

src := source.New("example.sysml", []byte("part Wheel;"))
span := source.Span{Start: 0, End: 4} // "part"


internal/core/lexer

Tokenization of SysML v2 textual notation.

Key Types:

  • Lexer — Scanner for SysML v2 tokens
  • Next() Token — Get next token
  • Peek() Token — Look ahead without consuming

  • Token — Single token with position

  • Kind TokenKind — Token type (keyword, identifier, literal, operator)
  • Span source.Span — Position in source
  • Text string — Raw text

  • TokenKind — Enum of all token types

  • Keywords: KwPackage, KwPart, KwAttribute, KwAction, etc. (~200 keywords)
  • Literals: LitInteger, LitReal, LitString, LitBool
  • Operators: OpPlus, OpMinus, OpEq, etc.
  • Structure: LBrace, RBrace, Semicolon, Comma, etc.

Usage:

lex := lexer.New(source.New("test", []byte("part Wheel { }")))
for tok := lex.Next(); tok.Kind != lexer.EOF; tok = lex.Next() {
    fmt.Println(tok.Kind, tok.Text)
}


internal/core/parser

Recursive-descent parser producing AST.

Entry Points:

  • New(src *source.SourceFile) *Parser — Create parser
  • (*Parser).ParseFile() *ast.RootNamespace — Parse complete file

Key Functions:

  • parseDefinition() — Parse def (part, attribute, action, etc.)
  • parseUsage() — Parse usage
  • parseExpression() — Parse expressions
  • parseActionBody() — Parse behavioral action body (Phase C3)

Error Recovery:

Parser always produces a complete tree. Errors result in ast.ErrorNode placeholders.

Usage:

p := parser.New(source.New("test.sysml", content))
root := p.ParseFile()
// root is always non-nil, check root.Errors for diagnostics


internal/core/ast

Abstract Syntax Tree nodes (syntax-only, immutable).

Node Interface:

All AST nodes implement:

type Node interface {
    Span() source.Span
    LeadingTrivia() []Trivia
    TrailingTrivia() []Trivia
}

Key Types:

Namespace & Elements: - RootNamespace — Top-level (one per file) - Package — Package declaration - Import — Import statement

Definitions & Usages: - Definition — Base for all defs (part, attribute, action, etc.) - .Kind DefinitionKind — Type of definition - .Ident *Identifier — Name - .Members []Node — Body contents - .Specializations []*QualifiedName — Generalization edges - .Visibility VisibilityKind

  • Usage — Base for all usages
  • .Kind UsageKind
  • .Ident *Identifier
  • .Members []Node
  • .Multiplicity *MultiplicityExpr
  • .Value Node — Default value expression

Expressions: - LiteralInteger, LiteralReal, LiteralBool, LiteralString - FeatureReference — Reference to feature by name - FeatureChainExpr — Dot notation (x.y.z) - UnaryExpr, BinaryExpr, ConditionalExpr - InvocationExpr — Function/calc invocation - SequenceExpr, CollectExpr, SelectExpr — Collection operations

Behavioral Nodes (Phase C3): - InitialNode, FinalNode — Control flow start/end - ForkNode, JoinNode — Concurrency - MergeNode, DecisionNode — Branching - ActionExecutionNode — Action invocation - SuccessionEdge — Flow between nodes - .Source, .Target *QualifiedName - .Guard Node — Optional guard expression

Architecture Rule: AST is immutable after parsing. All semantic information lives in side tables.


internal/core/symbols

Symbol tables and scope trees.

Key Types:

  • Symbol — Represents a declared name
  • Name string — Identifier
  • Kind SymbolKind — Type of symbol
  • Decl ast.Node — AST node that declares it
  • Scope *Scope — Child scope (if compound)
  • OwnerScope *Scope — Parent scope
  • Visibility ast.VisibilityKind

  • Scope — Lexical scope

  • Parent() *Scope
  • Node() ast.Node
  • Children() []*Scope
  • LookupLocal(name string) (*Symbol, bool) — Local lookup only
  • LookupLocalAll(name string) []*Symbol — All with that name (short+primary)
  • MemberNames() []string — All declared names in order

  • Index — Global symbol index

  • DocumentRoot(name string) *Scope — Get document root scope

Usage:

idx := symbols.NewIndex()
idx.AddDocument("example.sysml", root) // root is *ast.RootNamespace
scope := idx.DocumentRoot("example.sysml")
sym, ok := scope.LookupLocal("Wheel")

Important: Short name + primary name alias the same *Symbol. Dedupe by pointer when iterating.


internal/core/resolve

Name resolution (lazy, memoized).

Key Types:

  • Resolver — Name resolver
  • ResolveQualified(scope *symbols.Scope, qn *ast.QualifiedName) (*symbols.Symbol, bool)
  • ResolveUnqualified(scope *symbols.Scope, name string) (*symbols.Symbol, bool)
  • ResolveImport(importNode *ast.Import) []*symbols.Symbol

Usage:

res := resolve.New(index)
sym, ok := res.ResolveQualified(scope, qname)

Results are memoized internally.


internal/core/semantics

Type system, conformance, semantic queries.

Key Type: Model

Central semantic query engine. Built from resolver:

model := semantics.NewModel(resolver)

Methods:

Type relationships: - DirectSupertypes(sym *symbols.Symbol) []*symbols.Symbol — Immediate generalizations - AllSupertypes(sym *symbols.Symbol) []*symbols.Symbol — Transitive closure - Conforms(a, b *symbols.Symbol) bool — Type conformance check - HasSpecializationCycle(sym *symbols.Symbol) bool — Cycle detection

Members: - MembersOf(sym *symbols.Symbol) []*symbols.Symbol — All members (local + inherited with masking) - LookupMember(sym *symbols.Symbol, name string) (*symbols.Symbol, bool)

Multiplicity: - MultiplicityOf(sym *symbols.Symbol) (Range, bool) — Extract multiplicity bounds - Range{Lower, Upper Bound} - Bound{Value int64, Infinite bool, Known bool}

Constant evaluation: - Eval(n ast.Node) (Value, bool) — Constant-folder for literals and operators - Value{Kind ValueKind, Int int64, Real float64, Bool bool} - ValueKind ∈ {ValInt, ValReal, ValBool, ValInfinity, ValInvalid}

Note: Eval() is a constant-folder only. For full runtime evaluation, see internal/core/runtime.


internal/core/passes

Pluggable validation passes.

Architecture:

Validation runs in tiers: 1. Syntax — Checks for ErrorNodes 2. Name Resolution — Validates all names resolve 3. Type Checking — Type conformance 4. Constraints — Deep semantic rules

Higher tiers skip if lower tier fails.

Key Types:

  • Pass — Validation pass interface
  • Level() PassLevel — Which tier
  • Run(ctx *Context, name string, root *ast.RootNamespace) []Diagnostic

  • Context — Provides access to resolver and model

  • Resolver() *resolve.Resolver
  • Model() *semantics.Model

  • Diagnostic — Error/warning

  • Level DiagnosticLevel — Error, Warning, Info
  • Span source.Span
  • Message string

Usage:

// Analyze runs the default pass registry internally.
diagnostics := passes.Analyze("example.sysml", root, parseDiags, idx)


internal/core/runtime

Execution runtime (Tiers 1-5: instances, expressions, behaviors).

Key Types:

Value System:

  • Value — Runtime value
  • Kind ValueKind — Type tag
  • Const semantics.Value — Integer/real/bool (for ValConst)
  • Str string, Instance int64, Sequence *Sequence, Set *Set

  • ValueKind — Enum

  • ValConst — Integer/real/bool (stored in Const field)
  • ValNull, ValString, ValInstance, ValSequence, ValSet

Instance Model:

  • Instance — Runtime instance
  • ID int64 — Unique instance ID
  • Type *symbols.Symbol — Type symbol
  • FeatureValues map[string]*FeatureValue — Feature values by feature name

Execution Context:

  • Context — Runtime execution context
  • Instantiate(sym *symbols.Symbol) (*Instance, error) — Create instance
  • Eval(expr ast.Node, env map[*symbols.Symbol]Value) (Value, error) — Evaluate expression
  • InvokeCalc(sym *symbols.Symbol, args []Value, scope *symbols.Scope) (Value, error) — Invoke calculation
  • EvaluateConstraint(sym *symbols.Symbol, scope *symbols.Scope) (bool, error) — Evaluate constraint
  • EvaluateRequirement(sym *symbols.Symbol, scope *symbols.Scope) (bool, error) — Evaluate requirement
  • ExecuteAction(sym *symbols.Symbol) (map[string]Value, error) — Execute action to completion
  • ExecuteState(sym *symbols.Symbol) (map[string]Value, error) — Execute state machine until final/suspended
  • CreateActionExecutor(sym *symbols.Symbol) (*ActionExecutor, error) — Create action executor for debugging
  • CreateStateExecutor(sym *symbols.Symbol) (*StateExecutor, error) — Create state executor for debugging

Behavioral Execution (Tier 5):

  • Token — Control token for action execution, carrying no values of its own
  • ID int64 — Unique token ID
  • Location ast.Node — Current node (InitialNode, ActionExecutionNode, etc.)

  • ActionExecutor — Petri-net token-flow execution engine

  • Step() error — Advance all tokens one step
  • RunToCompletion() error — Execute until StateCompleted (max 10k steps)
  • Tokens() []Token — Get active tokens (copy)
  • State() ExecutionState — Current execution state (Ready/Running/Completed/Suspended)
  • Results() map[string]Value — Get results after completion
  • Data() map[string]Value — The action's live feature space, shared by every token
  • SetBreakpoint(nodeName string) — Set breakpoint on node
  • ClearBreakpoints() — Clear all breakpoints
  • ActionSymbol() *symbols.Symbol — Get action symbol

  • StateExecutor — Event-driven state machine execution

  • ProcessNextEvent() error — Process next event from queue
  • CurrentState() ast.Node — Get current StateNode
  • StateStack() []*ast.StateNode — Get active configuration (hierarchical states)
  • StateData() map[string]Value — Get state machine variables
  • EventQueue() *EventQueue — Get event queue
  • CurrentTime() float64 — Get simulation time
  • State() ExecutionState — Get execution state
  • StateMachineSymbol() *symbols.Symbol — Get state machine symbol

  • ExecutionState — Enum

  • StateReady — Initialized, not started
  • StateRunning — Executing
  • StateCompleted — Finished (final node/state reached)
  • StateSuspended — Paused (waiting for events)

  • Event — State machine event

  • ID int64 — Unique event ID
  • Type EventType — Time/Change/Accept/Call
  • Timestamp float64 — Event timestamp (for TimeEvent)
  • Payload map[string]Value — Event data

Built-in Functions:

Registered KerML builtins: - Arithmetic: +, -, *, /, %, ** - Comparison: ==, !=, <, >, <=, >= - Boolean: and, or, xor, not, implies - Collections: size, isEmpty, ->select, ->collect - String: + (concat), size, substring

Usage:

Tier 1-3 (Instances & Expressions):

// Honour the SYSML_MAX_* budgets instead of the defaults with:
//   budgets, err := runtime.BudgetsFromEnv()
//   err = ctx.SetBudgets(budgets)
ctx := runtime.NewContext(model, resolver, runtime.DefaultMaxSteps)
inst, _ := ctx.Instantiate(wheelSym)
fv, _ := inst.GetFeatureValue(ctx, "diameter")
result, _ := ctx.InvokeCalc(addSym, []Value{v1, v2}, scope)

Tier 5 (Actions):

// Execute action to completion
results, err := ctx.ExecuteAction(myActionSym)
if err != nil { /* handle error */ }
result := results["result"]

// Or debug step-by-step
exec, _ := ctx.CreateActionExecutor(myActionSym)
exec.Initialize()
for exec.State() != StateCompleted {
    exec.Step()
    tokens := exec.Tokens()
    // inspect tokens
}

Tier 5 (State Machines):

// Execute state machine
stateData, err := ctx.ExecuteState(stateMachineSym)
if err != nil { /* handle error */ }

// Or debug with events
exec, _ := ctx.CreateStateExecutor(stateMachineSym)
exec.Initialize()
for exec.State() != StateCompleted {
    exec.ProcessNextEvent()
    fmt.Printf("State: %s, Time: %f\n", exec.CurrentState(), exec.CurrentTime())
}


internal/core/model

Workspace and document management.

Key Types:

  • Workspace — Multi-file workspace
  • AddDocument(name string, src *source.SourceFile) *Document
  • GetDocument(name string) (*Document, bool)
  • RemoveDocument(name string)
  • Index() *symbols.Index — Global symbol index
  • Diagnostics(name string) []passes.Diagnostic

  • Document — Single source file

  • Name() string
  • Source() *source.SourceFile
  • Root() *ast.RootNamespace
  • Scope() *symbols.Scope
  • Version() int — Increments on update

Usage:

ws := model.NewWorkspace()
doc := ws.AddDocument("example.sysml", src)
diagnostics := ws.Diagnostics("example.sysml")


internal/core/libs

Standard library bundling and caching.

Key Functions:

  • Load(name string) (*source.SourceFile, error) — Load stdlib file
  • ListFiles() []string — All stdlib files

Standard library is embedded in the binary using Go embed.FS.


Frontend Packages

internal/lsp

Language Server Protocol implementation.

Key Type:

  • Server — LSP server
  • Run(ctx context.Context, conn io.ReadWriteCloser) error

Transport: stdio only. sysml-lsp also accepts --stdio/-stdio as an explicit no-op, because standard language clients (including the bundled VS Code extension) name the transport on the command line. Any other unknown flag is still rejected with exit status 2.

Lifecycle (LSP 3.17): shutdown is answered, after which every request other than exit is answered InvalidRequest (-32600) and non-exit notifications are dropped. exit makes Run return and the process terminate — status 0 after a preceding shutdown, 1 otherwise.

Current Capabilities: - Document synchronization (open/change/close) - Diagnostics (syntax + semantic errors) - Hover (type info) - Go to definition - References - Document symbols - Workspace symbols - Completion

Usage:

ws := model.NewWorkspace()
srv := lsp.NewServer(ws)
srv.Run(ctx, stdio{}) // stdio implements io.ReadWriteCloser


internal/repl

Interactive REPL implementation.

Key Types:

  • Session — REPL session state
  • Accumulates declarations across inputs
  • Tracks runtime context and instances

Entry Point:

  • Loop(reader LineReader, out io.Writer, session *Session) error

LineReader Interface:

type LineReader interface {
    ReadLine(prompt string) (string, error)
}

Meta Commands: - %help, %list, %clear, %load <file> - %search <substring> — List the declared and library symbols whose qualified name contains the substring, with the kind of each - %builtins — List the library functions the runtime implements directly - %instantiate <name>, %features <name>, %instances - %eval <expr> - %calc <name> [args...] — Invoke calculation with arguments - %constraint <name> — Evaluate constraint - %requirement <name> — Evaluate requirement - %satisfy [name] — Evaluate satisfaction assertions (assert satisfy <requirement> by <part>;), every one in the model or the ones a named element states

Usage:

session := repl.NewSession()
repl.Loop(reader, os.Stdout, session)


SysML v2 API & Services Query

The gRPC service implements the query surface the SysML v2 API & Services standard defines, so a client that speaks that API — the SysML-v2-API-Java-Client, the SysML v2 API Cookbook notebooks, MATLAB System Composer's executeQuery — can filter a model OpenSysML parsed. The standard's schema is authoritative: api/openapi.yaml in the Java client, components Query, Constraint, PrimitiveConstraint, CompositeConstraint.

Implementation: internal/grpc/query.go (Service.Query), reported from GetServerInfo as the query capability. Python: model.query(...) (python/opensysml/query.py).

The query model

Query          scope[]    elements to consider; empty is the whole loaded model
               select[]   properties to report; empty reports every one
               where      one Constraint; absent selects the whole scope

Constraint     PrimitiveConstraint | CompositeConstraint      (a protobuf oneof)

PrimitiveConstraint   property, operator (= > <), value[], inverse
CompositeConstraint   operator (and | or), constraint[]        (nests arbitrarily)

The RPC takes the same shape, with the standard's JSON names preserved, so translation from the standard's JSON is mechanical:

rpc Query(QueryRequest) returns (QueryResponse);   // api/proto/sysml.proto

A cookbook payload, sent verbatim through the Python client:

model = opensysml.load("examples/vehicle.sysml")
model.query({"@type": "Query", "where": {
    "@type": "PrimitiveConstraint",
    "operator": "=", "property": "@type", "value": ["PartUsage"]}})

Each answered element is @id (its qualified name), @type and the selected properties it has. A property an element does not have is absent, not empty.

An element with no qualified identity — an unnamed doc, an anonymous usage, an anonymous connect — is not answered: its qualified name has an empty segment (Demo::), so it is neither unique nor a name a scope could use. The standard identifies an element by @id, and such an element has none (TestQueryOmitsElementsWithNoQualifiedIdentity).

Neither is one declared inside an action body — a branch of an if, a loop body — since the body is owned by no element and so names its declarations only locally (step, not Demo::Drive::step): that name identifies no element and could not be used as a scope (TestQueryOmitsBodyLocalDeclarations). An answered @id is always a qualified name that the model resolves back to that element.

Queryable properties

The set is closed and is the single source of truth in queryProperties (internal/grpc/query.go). A property outside it is an INVALID_ARGUMENT error listing the ones that exist — never a silently empty answer.

Property Reports Ordered
@id The element's qualified name, which is also how scope names it
qualifiedName Same as @id
@type The element's metamodel type (table below)
name The element's own name, the last segment of its qualified name
declaredName name, absent when the name is an effective name borrowed from a referenced feature
owner Qualified name of the owning element; absent for a top-level element, whose owner is the document root
isAbstract true/false for a definition or usage; absent for anything else, and for a standard-library element restored from cache, which carries no declaration
type Qualified name of the resolved type of a typed feature; absent when untyped or unresolved
multiplicityLower Declared lower bound
multiplicityUpper Declared upper bound, * when unbounded

@type — symbol kind → metamodel type

Mapping OpenSysML's symbol kinds onto the standard's metamodel type names is the substantive design decision here; metamodelTypeNames (internal/grpc/query.go) is the single source of truth, and TestMetamodelTypeNameCoversEveryKind keeps it total over every kind a parsed declaration can have. A standard-library element restored from cache may carry no kind at all, and then reports no @type: it is answered, but never matches a @type = comparison (and is kept by the inverse of one).

OpenSysML kind @type
package, namespace Package, Namespace
partDef / partUsage PartDefinition / PartUsage
attributeDef / attributeUsage AttributeDefinition / AttributeUsage
itemDef / itemUsage ItemDefinition / ItemUsage
occurrenceDef / occurrenceUsage OccurrenceDefinition / OccurrenceUsage
portDef / portUsage PortDefinition / PortUsage
interfaceDef / interfaceUsage InterfaceDefinition / InterfaceUsage
connectionDef / connectionUsage ConnectionDefinition / ConnectionUsage
flowDef / flowUsage, allocationDef / allocationUsage FlowDefinition / FlowUsage, AllocationDefinition / AllocationUsage
actionDef / actionUsage, stateDef / stateUsage ActionDefinition / ActionUsage, StateDefinition / StateUsage
calcDef / calcUsage CalculationDefinition / CalculationUsage
constraintDef / constraintUsage, requirementDef / requirementUsage ConstraintDefinition / ConstraintUsage, RequirementDefinition / RequirementUsage
caseDef / caseUsage and the analysis / verification / use-case forms CaseDefinition / CaseUsage, AnalysisCase…, VerificationCase…, UseCase…
viewDef, viewpointDef, renderingDef, concernDef and their usages ViewDefinition, ViewpointDefinition, RenderingDefinition, ConcernDefinition and …Usage
enumerationDef / enumerationUsage, metadataDef / metadataUsage, metaclass EnumerationDefinition / EnumerationUsage, MetadataDefinition / MetadataUsage, Metaclass
comment, documentation, textualRepresentation, dependency Comment, Documentation, TextualRepresentation, Dependency

Three kinds have no distinct metamodel type, and report the closest one that exists — documented as approximations rather than hidden:

OpenSysML kind @type Why
individualDef / individualUsage OccurrenceDefinition / OccurrenceUsage An individual is an occurrence with isIndividual set, not a type of its own
connectorEnd Feature A connector end is a Feature with isEnd set
alias Membership An alias is a named membership, not an element

Comparison semantics

Where the standard is vague, these are the choices this implementation makes:

  • = compares the property's value as text, and matches if it equals any of the listed values. The standard writes one value and its clients write a list for @type; one value is the degenerate case of the same rule.
  • > and < compare numbers, and require exactly one operand and an ordered property (the multiplicity* pair). A non-ordered property, more than one operand, or an operand that is not a number is an INVALID_ARGUMENT error, not a false verdict. * parses as infinity, so multiplicityUpper > 1 holds for an unbounded feature.
  • An element that simply has no value for the property fails the comparison — that is a fact about the element, not a fault in the query.
  • inverse negates the verdict of its own constraint, so a constraint and its inverse partition the scope.
  • and/or combine nested verdicts, short-circuiting once one is decisive.
  • The whole where tree is judged before any element is read, so a fault in it — an unknown property, a missing operator, an empty composite, > on an unordered property — is reported whatever the scope holds, including a model that declares nothing (TestQueryFaultIsReportedWithNoElementsToConsider), and wherever it sits, including under an already-decisive sibling.
  • scope considers each named element and everything nested inside it, in declaration order, parents first; a name the model does not have is an error. An empty scope enumerates the parsed document (not the standard library, which is reachable by naming a library element as a scope).

Not supported — by design of the standard

The standard's query model is deliberately weak, and this is an interop surface, not OpenSysML's expressive query story:

  • No graph traversal and no transitive closure. There is no "all elements under X", no "everything that specializes Y", no path expressions and no joins. Containment is expressible only as a scope; specialization is not expressible at all, even though semantics.Model can answer it.
  • No owningProject / @id on the query resource (single-model service, no project or commit store), no derived or computed properties, and no ordering or paging of results — elements come back in declaration order.

Usage Examples

Parse a File

import (
    "github.com/Open-MBEE/OpenSysML/internal/core/source"
    "github.com/Open-MBEE/OpenSysML/internal/core/parser"
)

src := source.New("example.sysml", []byte(`
    part Wheel {
        attribute diameter : Real;
    }
`))

p := parser.New(src)
root := p.ParseFile()
// root is always non-nil, check root.Errors for parse errors

Build Symbol Table

import (
    "github.com/Open-MBEE/OpenSysML/internal/core/symbols"
)

idx := symbols.NewIndex()
idx.AddDocument("example.sysml", root)
scope := idx.DocumentRoot("example.sysml")
sym, ok := scope.LookupLocal("Wheel")

Resolve Names

import (
    "github.com/Open-MBEE/OpenSysML/internal/core/resolve"
)

res := resolve.New(idx)
sym, ok := res.ResolveQualified(scope, qualifiedName)

Type Queries

import (
    "github.com/Open-MBEE/OpenSysML/internal/core/semantics"
)

model := semantics.NewModel(res)
members := model.MembersOf(wheelSym)
conforms := model.Conforms(wheelSym, vehiclePartSym)

Run Validation

import (
    "github.com/Open-MBEE/OpenSysML/internal/core/passes"
)

// Analyze wires up the default pass registry and context internally.
diagnostics := passes.Analyze("example.sysml", root, parseDiags, idx)

Execute Runtime

import (
    "github.com/Open-MBEE/OpenSysML/internal/core/runtime"
)

rtCtx := runtime.NewContext(model, resolver, runtime.DefaultMaxSteps)
inst, _ := rtCtx.Instantiate(wheelSym)
diameter, _ := inst.GetFeatureValue(rtCtx, "diameter")
fmt.Println(diameter.Value) // Value{Kind: ValConst, Real: 16.0}

Architecture Principles

1. Immutable AST

AST nodes are syntax-only and immutable after parsing. All semantic information (types, resolved references, instance values) lives in side tables keyed by ast.Node or *symbols.Symbol.

2. Lazy & Memoized

  • Name resolution: computed on-demand, cached
  • Semantic queries: computed on-demand, cached
  • Passes: run only when diagnostics requested

3. Incremental Analysis

Documents can be updated individually. Symbol index and caches invalidate only affected documents.

4. Separation of Concerns

Source → Lexer → Parser → AST
                     Symbols (side table)
                     Resolve (side table)
                    Semantics (side table)
                       Passes (diagnostics)
                      Runtime (values, instances)

Each layer is independent and testable.


Testing

All packages have comprehensive test coverage:

go test ./internal/core/parser    # Parser tests
go test ./internal/core/symbols   # Symbol table tests
go test ./internal/core/semantics # Semantic tests
go test ./internal/core/runtime   # Runtime tests
go test ./...                     # All tests

Test fixtures in testdata/*.sysml.


Further Reading