Documentation
¶
Overview ¶
Package interpreter provides the CalcMark expression interpreter.
The interpreter executes validated Abstract Syntax Trees (ASTs) and computes results while maintaining variable bindings in an environment.
Architecture ¶
The interpreter follows a tree-walking pattern:
- Traverse the AST nodes
- Evaluate each node recursively
- Store variable assignments in environment
- Return computed values as typed results
Usage ¶
Basic interpretation:
env := interpreter.NewEnvironment()
interp := interpreter.NewInterpreterWithEnv(env)
nodes, _ := parser.Parse("x = 5\ny = x + 3\n")
results, err := interp.Eval(nodes)
if err != nil {
log.Fatal(err)
}
for _, result := range results {
fmt.Println(result.String())
}
Environment ¶
The environment tracks variable bindings:
env := interpreter.NewEnvironment()
env.Set("x", types.NewNumber(decimal.NewFromInt(5)))
value, exists := env.Get("x")
Variables persist across evaluations when using the same environment, enabling line-by-line evaluation in interactive editors.
Type System Integration ¶
The interpreter produces typed values:
- types.Number: Arbitrary-precision decimals
- types.Currency: Monetary values with symbols
- types.Quantity: Physical quantities with units
- types.Date: Calendar dates
- types.Time: Time of day
- types.Duration: Time intervals
- types.Boolean: True/false values
Operators ¶
Supported operations:
- Arithmetic: +, -, *, /, %, ^
- Comparison: ==, !=, <, >, <=, >=
- Unary: -, +
Functions ¶
Built-in functions:
- avg(...): Average of values (variadic)
- sqrt(x): Square root
Unit Handling ¶
The interpreter implements special semantics for units:
Binary operations (first-unit-wins):
5 kg + 10 lb → ~9.54 kg (converts lb to kg, keeps first unit) $100 + €50 → ~$155.00 (converts EUR to USD based on rates) 10 m + 5 ft → ~11.52 m (converts ft to m)
Functions (drop mixed units):
avg($100, $200) → $150.00 (same unit preserved) avg($100, €200) → 150 (mixed units → dimensionless number) avg(5 kg, 10 kg) → 7.5 kg (same unit preserved) avg(5 kg, 10 lb) → (error: incompatible units)
Error Handling ¶
Runtime errors include:
- Undefined variables
- Type mismatches
- Division by zero
- Incompatible units
Performance ¶
The interpreter is designed for interactive use and completes evaluations in microseconds. See benchmark tests for metrics.
Index ¶
- Variables
- func Evaluate(source string, env *Environment) error
- func ExtractNumber(val types.Type) (*types.Number, error)
- func GetCategoryOrder() []string
- func GetFunctionNames() []string
- func GetFunctionsByCategory() map[string][]FunctionInfo
- type DirectiveResolver
- type Environment
- func (e *Environment) Clone() *Environment
- func (e *Environment) Get(name string) (types.Type, bool)
- func (e *Environment) GetAllVariables() map[string]types.Type
- func (e *Environment) GetExchangeRate(from, to string) (decimal.Decimal, bool)
- func (e *Environment) Has(name string) bool
- func (e *Environment) HasExchangeRates() bool
- func (e *Environment) Set(name string, value types.Type)
- func (e *Environment) SetExchangeRate(from, to string, rate decimal.Decimal)
- type FunctionDef
- type FunctionInfo
- type Interpreter
Constants ¶
This section is empty.
Variables ¶
var BuiltinFunctions = []FunctionDef{ { Name: "avg", Synonyms: []string{"average", "mean"}, Description: "Calculate the average of values", Signature: "avg(value1, value2, ...)", Category: "Math", }, { Name: "sum", Synonyms: []string{}, Description: "Calculate the sum of values", Signature: "sum(value1, value2, ...)", Category: "Math", }, { Name: "sqrt", Synonyms: []string{}, Description: "Calculate square root of a number", Signature: "sqrt(value)", Category: "Math", }, { Name: "number", Synonyms: []string{}, Description: "Extract the numeric value from any type", Signature: "number(value)", Category: "Math", }, { Name: "accumulate", Synonyms: []string{}, Description: "Accumulate a rate over time (e.g., requests/sec over 1 day)", Signature: "accumulate(rate, time_period)", Category: "Math", }, { Name: "convert_rate", Synonyms: []string{}, Description: "Convert rate to different time unit", Signature: "convert_rate(rate, time_unit)", Category: "Conversion", }, { Name: "downtime", Synonyms: []string{}, Description: "Calculate downtime from availability percentage", Signature: "downtime(availability_percent, time_period)", Category: "Network", }, { Name: "rtt", Synonyms: []string{}, Description: "Get round-trip time for network scope", Signature: "rtt(scope)", Category: "Network", }, { Name: "throughput", Synonyms: []string{}, Description: "Get throughput for network type", Signature: "throughput(network_type)", Category: "Network", }, { Name: "transfer_time", Synonyms: []string{}, Description: "Calculate data transfer time", Signature: "transfer_time(size, scope, network_type)", Category: "Network", }, { Name: "read", Synonyms: []string{}, Description: "Calculate storage read time", Signature: "read(size, storage_type)", Category: "Storage", }, { Name: "seek", Synonyms: []string{}, Description: "Get storage seek latency", Signature: "seek(storage_type)", Category: "Storage", }, { Name: "compress", Synonyms: []string{}, Description: "Calculate compression time", Signature: "compress(size, compression_type)", Category: "Storage", }, { Name: "capacity", Synonyms: []string{}, Description: "Calculate required capacity for demand", Signature: "capacity(demand, capacity_per_unit, unit, buffer?)", Category: "Capacity", }, { Name: "compound", Synonyms: []string{}, Description: "Compound growth: A = P(1+r)^n", Signature: "compound(principal, rate, duration, period?)", Category: "Growth", }, { Name: "grow", Synonyms: []string{}, Description: "Linear growth: A = P + (increment x periods)", Signature: "grow(starting_amount, increment, duration)", Category: "Growth", }, { Name: "depreciate", Synonyms: []string{}, Description: "Declining balance depreciation", Signature: "depreciate(principal, rate, duration, salvage?)", Category: "Growth", }, }
BuiltinFunctions is the single source of truth for all CalcMark functions. Adding a function here automatically makes it available in help, autocomplete, and the interpreter. Missing any field will cause a compile-time or test failure. Note: Eval fields are populated in init() to avoid initialization cycles.
Functions ¶
func Evaluate ¶
func Evaluate(source string, env *Environment) error
Evaluate is a convenience wrapper for parsing and evaluating CalcMark source. This provides API compatibility with the old evaluator package.
func ExtractNumber ¶ added in v1.7.0
ExtractNumber extracts the decimal value from any CalcMark type as a plain Number. Exported so that operator coercion (Quantity × Currency) can reuse the logic.
func GetCategoryOrder ¶
func GetCategoryOrder() []string
GetCategoryOrder returns category names in their registration order (first-occurrence order from BuiltinFunctions).
func GetFunctionNames ¶
func GetFunctionNames() []string
GetFunctionNames returns all function names including synonyms. Reads from BuiltinFunctions (single source of truth).
func GetFunctionsByCategory ¶
func GetFunctionsByCategory() map[string][]FunctionInfo
GetFunctionsByCategory returns functions grouped by category. Reads from BuiltinFunctions (single source of truth).
Types ¶
type DirectiveResolver ¶ added in v1.6.2
type DirectiveResolver interface {
// ScaleFactor returns the scale factor and true if scale is defined.
ScaleFactor() (decimal.Decimal, bool)
// ResolveGlobal returns the typed value of a global and true if it exists.
ResolveGlobal(name string) (types.Type, bool, error)
}
DirectiveResolver provides access to frontmatter values for @directive references. This interface breaks the import cycle between impl/interpreter and spec/document.
type Environment ¶
type Environment struct {
// contains filtered or unexported fields
}
Environment tracks variable bindings during interpretation. This is separate from Go's context.Context - it's simply variable storage for CalcMark variables.
func NewEnvironment ¶
func NewEnvironment() *Environment
NewEnvironment creates a new empty environment with built-in constants.
func (*Environment) Clone ¶
func (e *Environment) Clone() *Environment
Clone creates a shallow copy of the environment.
func (*Environment) Get ¶
func (e *Environment) Get(name string) (types.Type, bool)
Get retrieves a variable binding. Returns the value and true if found, nil and false if not found.
func (*Environment) GetAllVariables ¶
func (e *Environment) GetAllVariables() map[string]types.Type
GetAllVariables returns the map of all variables (for sync with semantic checker).
func (*Environment) GetExchangeRate ¶
func (e *Environment) GetExchangeRate(from, to string) (decimal.Decimal, bool)
GetExchangeRate retrieves an exchange rate for currency conversion. Returns the rate and true if found, zero and false if not defined.
func (*Environment) Has ¶
func (e *Environment) Has(name string) bool
Has checks if a variable is defined.
func (*Environment) HasExchangeRates ¶
func (e *Environment) HasExchangeRates() bool
HasExchangeRates returns true if any exchange rates are defined.
func (*Environment) Set ¶
func (e *Environment) Set(name string, value types.Type)
Set stores a variable binding.
func (*Environment) SetExchangeRate ¶
func (e *Environment) SetExchangeRate(from, to string, rate decimal.Decimal)
SetExchangeRate sets an exchange rate for currency conversion. Key format: "FROM_TO" (e.g., "USD_EUR").
type FunctionDef ¶
type FunctionDef struct {
Name string // Primary name (e.g., "avg")
Synonyms []string // Alternative names (e.g., ["average", "mean"])
Description string // Human-readable description
Signature string // Usage pattern (e.g., "avg(value1, value2, ...)")
Category string // Grouping: "Math", "Conversion", "Network", "Storage", "Capacity"
// Eval is the function implementation. It receives the interpreter (for evaluating
// arguments) and the AST node (for accessing raw arguments when needed).
// This field is populated in init() to avoid initialization cycles.
Eval func(interp *Interpreter, f *ast.FunctionCall) (types.Type, error)
}
FunctionDef contains function metadata and implementation in one place. This is the single source of truth for help, autocomplete, and diagnostics.
type FunctionInfo ¶
type FunctionInfo struct {
Name string // Primary function name (e.g., "avg")
Synonyms []string // Alternative names (e.g., ["average", "mean"])
Description string // Human-readable description
Signature string // Usage pattern (e.g., "avg(value1, value2, ...)")
Category string // Grouping for help display (e.g., "Math", "Network", "Storage")
}
FunctionInfo contains metadata about a CalcMark function for help output. This is derived from FunctionDef but excludes the Eval function pointer.
func GetAllFunctions ¶
func GetAllFunctions() []FunctionInfo
GetAllFunctions returns all registered functions sorted by name. Reads from BuiltinFunctions (single source of truth).
func GetFunctionByName ¶
func GetFunctionByName(name string) (FunctionInfo, bool)
GetFunctionByName looks up a function by name or synonym. Returns the FunctionInfo and true if found, or empty FunctionInfo and false if not.
type Interpreter ¶
type Interpreter struct {
// contains filtered or unexported fields
}
Interpreter executes validated AST nodes and produces typed results. This is a Go-specific implementation of CalcMark execution.
func NewInterpreter ¶
func NewInterpreter() *Interpreter
NewInterpreter creates a new interpreter with an empty environment.
func NewInterpreterWithEnv ¶
func NewInterpreterWithEnv(env *Environment) *Interpreter
NewInterpreterWithEnv creates a new interpreter with a pre-populated environment.
func (*Interpreter) Eval ¶
Eval executes a list of AST nodes and returns the results. Each node produces a typed value.
func (*Interpreter) GetEnvironment ¶
func (interp *Interpreter) GetEnvironment() *Environment
GetEnvironment returns the interpreter's environment.
func (*Interpreter) SetDirectiveResolver ¶ added in v1.6.2
func (interp *Interpreter) SetDirectiveResolver(dr DirectiveResolver)
SetDirectiveResolver provides frontmatter context for resolving @directive references.
func (*Interpreter) SetMeasurement ¶ added in v1.8.4
func (interp *Interpreter) SetMeasurement(mc *units.MeasurementConfig)
SetMeasurement configures how bare ambiguous unit names are resolved. This is a pre-interpreter directive: it affects how unit names are interpreted before any evaluation occurs (unlike convert_to which is post-evaluation).
Source Files
¶
- availability_functions.go
- capacity_functions.go
- compression_functions.go
- datetime.go
- doc.go
- environment.go
- evaluate.go
- fraction_ops.go
- functions.go
- growth_functions.go
- helpers.go
- interpreter.go
- literals.go
- napkin.go
- napkin_eval.go
- network_functions.go
- operators.go
- percentage_of_eval.go
- precise_eval.go
- rate_eval.go
- rate_functions.go
- registry.go
- speed_bridge.go
- storage_functions.go
- unit_conversion.go
- unit_conversion_eval.go
- variables.go