Documentation
¶
Index ¶
- func EvaluateExpr(expr *ExprIR, getValue ValueLookup) (any, error)
- func IsTruthy(v any) bool
- func Plan(events []parser.Event, tokens []lexer.Token, config Config) (*planfmt.Plan, error)
- func RenderCommand(cmd *CommandExpr, displayIDs map[string]string) string
- func RenderExpr(expr *ExprIR, displayIDs map[string]string) string
- type ArgIR
- type BlockerIR
- type BlockerKind
- type CallTraceStmtIR
- type CommandExpr
- type CommandStmtIR
- type Config
- type DebugEvent
- type DebugLevel
- type DecoratorRef
- type DecoratorResolutionMetrics
- type Emitter
- type EnumMemberIR
- type EnumTypeIR
- type EvalError
- type ExecutionGraph
- type ExprIR
- type ExprKind
- type FunctionArg
- type FunctionCallStmtIR
- type FunctionIR
- type LoopIteration
- type ParamIR
- type PlanError
- type PlanResult
- type PlanTelemetry
- type ResolveConfig
- type ResolveResult
- type Resolver
- type ScopeStack
- type SourceSpan
- type StatementIR
- type StatementKind
- type StructFieldIR
- type StructTypeIR
- type TelemetryLevel
- type TryIR
- type ValueLookup
- type VarDeclIR
- type WhenArmIR
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func EvaluateExpr ¶
func EvaluateExpr(expr *ExprIR, getValue ValueLookup) (any, error)
EvaluateExpr evaluates an expression using a lookup function for values. Used during the resolution phase to evaluate conditions like `@var.ENV == "prod"`.
The getValue function looks up values by name:
- For ExprVarRef, looks up by variable name (e.g., "ENV")
- For ExprDecoratorRef, looks up by decorator key (e.g., "env.HOME")
For ExprBinaryOp, recursively evaluates operands and applies the operator.
func IsTruthy ¶
IsTruthy determines if a value is truthy. Follows common scripting language conventions: - nil is falsy - false is falsy - 0 (any numeric type) is falsy - "" (empty string) is falsy - Everything else is truthy
func RenderCommand ¶
func RenderCommand(cmd *CommandExpr, displayIDs map[string]string) string
RenderCommand renders a command expression to a string with DisplayID placeholders. Concatenates all parts, rendering each according to its type. Trims leading and trailing whitespace from the result.
func RenderExpr ¶
RenderExpr renders an expression to a string with DisplayID placeholders. Used during the emit phase to build plan commands.
For literals, returns the string representation. For var refs and decorator refs, looks up the DisplayID in the map. Binary ops are not supported (they're for conditions, not interpolation).
Types ¶
type BlockerIR ¶
type BlockerIR struct {
Kind BlockerKind
Depth int // Nesting level (for wave-based resolution)
Condition *ExprIR // Condition expression (for if/when)
ThenBranch []*StatementIR // Statements if condition is true
ElseBranch []*StatementIR // Statements if condition is false (optional)
Taken *bool // Set by Resolver: true=then, false=else, nil=unresolved
// For-loop specific
LoopVar string // Loop variable name (for "for x in ...")
Collection *ExprIR // Collection expression (for "for x in collection")
// Set by Resolver for for-loops: resolved iterations with deep-copied body
Iterations []LoopIteration
// When-specific (pattern matching)
Arms []*WhenArmIR // Pattern arms (for "when expr { pattern -> ... }")
// Set by Resolver for when: index of the matched arm (-1 if none)
MatchedArm int
}
BlockerIR represents control flow that blocks execution until resolved. The Resolver evaluates conditions and sets the Taken flag.
After resolution:
- if/when: Taken is set, untaken branch is nil (pruned)
- for: Iterations contains resolved loop iterations with deep-copied body
type BlockerKind ¶
type BlockerKind int
BlockerKind identifies the type of control flow blocker.
const ( BlockerIf BlockerKind = iota // if condition { ... } else { ... } BlockerWhen // when expr { pattern -> ... } BlockerFor // for item in collection { ... } )
type CallTraceStmtIR ¶
type CallTraceStmtIR struct {
Label string // e.g. deploy(token=opal:abc123) in rendered output
Block []*StatementIR // Fully resolved expanded statements
}
CallTraceStmtIR wraps expanded statements with function-call provenance. This is display-only metadata and does not affect execution semantics.
type CommandExpr ¶
type CommandExpr struct {
Parts []*ExprIR // Sequence of literals and expression references
}
CommandExpr represents a command with interpolated expressions. A command like `echo "Hello @var.NAME"` becomes:
Parts: [Literal("echo \"Hello "), VarRef("NAME"), Literal("\"")]
Named CommandExpr to distinguish expression-level command parts from statement-level command IR types.
type CommandStmtIR ¶
type CommandStmtIR struct {
Decorator string // "@shell", "@retry", etc.
Command *CommandExpr // The command with interpolated expressions
Args []ArgIR // Decorator arguments
Block []*StatementIR // Nested statements (for decorator blocks)
Operator string // "&&", "||", "|", ";" - chain to next command
RedirectMode string // ">", ">>" - redirect mode (empty if no redirect)
RedirectTarget *CommandExpr // For redirect operators, the target path (nil otherwise)
}
CommandStmtIR represents a command statement.
type Config ¶
type Config struct {
Target string // Command name (e.g. "hello") or "" for script mode.
Args []FunctionArg // Optional target function arguments (positional + named).
Context context.Context // Optional planning context for cancellation/deadlines.
IDFactory secret.IDFactory // Optional deterministic placeholder factory.
Vault *vault.Vault // Optional shared vault for value storage/scrubbing.
PlanSalt []byte // Optional deterministic salt (32 bytes) for contract verification.
Telemetry TelemetryLevel // Telemetry level (production-safe).
Debug DebugLevel // Debug level (development only).
}
Config configures planner behavior.
type DebugEvent ¶
DebugEvent captures planner trace/debug events.
type DebugLevel ¶
type DebugLevel int
DebugLevel controls debug tracing.
const ( DebugOff DebugLevel = iota DebugPaths DebugDetailed )
type DecoratorRef ¶
type DecoratorRef struct {
Name string // Decorator name: "env", "aws", "var", "shell", etc.
Selector []string // Property path: ["HOME"], ["secret", "api_key"]
Args []*ExprIR // For parameterized decorators: @retry(3, "1s")
ArgNames []string // Argument keys aligned with Args (canonical names; empty means positional source)
}
DecoratorRef is a structured decorator reference. Represents @decorator.selector.path with optional arguments.
type DecoratorResolutionMetrics ¶
type DecoratorResolutionMetrics struct {
TotalCalls int
BatchCalls int
BatchSizes []int
TotalTime time.Duration
SkippedCalls int
}
DecoratorResolutionMetrics tracks per-decorator resolution metrics.
type Emitter ¶
type Emitter struct {
// contains filtered or unexported fields
}
Emitter transforms a resolved IR tree into a planfmt.Plan. It traverses the pruned tree from the Resolver and builds: - planfmt.Step for each command - SecretUses for contract verification - DisplayID placeholders for secrets
func NewEmitter ¶
func NewEmitter(result *ResolveResult, v *vault.Vault, scopes *ScopeStack, target string) *Emitter
NewEmitter creates a new Emitter.
type EnumMemberIR ¶
EnumMemberIR represents a single enum member declaration.
type EnumTypeIR ¶
type EnumTypeIR struct {
Name string
BaseType string
Members []EnumMemberIR
Span SourceSpan
}
EnumTypeIR represents a top-level user-defined enum declaration.
type EvalError ¶
type EvalError struct {
Message string
VarName string // Variable or decorator name (if applicable)
Span SourceSpan // Source location (if available)
}
EvalError represents an error during expression evaluation.
type ExecutionGraph ¶
type ExecutionGraph struct {
Statements []*StatementIR // Top-level statements (script mode)
Functions map[string]*FunctionIR // Function definitions (command mode)
Types map[string]*StructTypeIR
Enums map[string]*EnumTypeIR
Scopes *ScopeStack // Variable scopes (name → exprID)
}
ExecutionGraph is the complete execution graph built from parser events. Contains all possible execution paths - the Resolver determines which are taken.
type ExprIR ¶
type ExprIR struct {
Kind ExprKind
Span SourceSpan
// For ExprLiteral - the actual value (string, int, bool, etc.)
Value any
// For ExprVarRef - the variable name (without @var. prefix)
VarName string
// For ExprDecoratorRef - structured decorator reference
Decorator *DecoratorRef
// For ExprEnumMemberRef - enum type and member
EnumName string
EnumMember string
// For ExprBinaryOp - operator and operands
Op string // "==", "!=", "&&", "||", "<", ">", "<=", ">="
Left *ExprIR // Left operand
Right *ExprIR // Right operand
// For ExprTypeCast - target type and optionality
TypeName string // Target type name (String, Int, Object, etc.)
Optional bool // True for Type? casts
}
ExprIR is the unified expression representation. Used for both conditions (if @var.X == "prod") and command interpolation (echo @var.X). This unification prevents divergence between two separate expression systems.
type ExprKind ¶
type ExprKind int
ExprKind identifies the type of expression.
const ( ExprLiteral ExprKind = iota // Literal value (string, int, bool) ExprVarRef // Variable reference (@var.X) ExprDecoratorRef // Decorator reference (@env.HOME, @aws.secret.key) ExprEnumMemberRef // Enum member reference (Type.Member) ExprBinaryOp // Binary operation (==, !=, &&, ||) ExprTypeCast // Type cast (expr as Type, expr as Type?) )
type FunctionArg ¶
FunctionArg represents one target function argument. Empty Name means positional argument.
type FunctionCallStmtIR ¶
type FunctionCallStmtIR struct {
Name string // Function name
Args []ArgIR // Call arguments (positional and named)
}
FunctionCallStmtIR represents a function call statement.
type FunctionIR ¶
type FunctionIR struct {
Name string
Params []ParamIR // Function parameters
Body []*StatementIR
Span SourceSpan
Scopes *ScopeStack // Scope snapshot for command mode prelude
}
FunctionIR represents a function definition.
type LoopIteration ¶
type LoopIteration struct {
Value any // Loop variable value for this iteration
Body []*StatementIR // Deep-copied statements for this iteration
}
LoopIteration represents one iteration of a resolved for-loop. Each iteration has its own deep-copied body to allow independent resolution of nested blockers (e.g., if statements inside the loop).
type ParamIR ¶
type ParamIR struct {
Name string
Type string // Type annotation (optional)
Default *ExprIR // Default value (optional)
}
ParamIR represents a function parameter.
type PlanError ¶
type PlanError struct {
Message string
Context string
EventPos int
TotalEvents int
Suggestion string
Example string
}
PlanError represents a planning error with contextual hints.
type PlanResult ¶
type PlanResult struct {
Plan *planfmt.Plan
PlanTime time.Duration
Telemetry *PlanTelemetry
DebugEvents []DebugEvent
}
PlanResult holds a generated plan and optional observability data.
func PlanWithObservability ¶
func PlanWithObservability(events []parser.Event, tokens []lexer.Token, config Config) (*PlanResult, error)
PlanWithObservability is the canonical observability entrypoint.
type PlanTelemetry ¶
type PlanTelemetry struct {
EventCount int
StepCount int
DecoratorResolutions map[string]*DecoratorResolutionMetrics
}
PlanTelemetry holds planner metrics.
type ResolveConfig ¶
type ResolveConfig struct {
TargetFunction string // Empty = script mode, non-empty = command mode
FunctionArgs []FunctionArg // Optional command-mode function arguments
Context context.Context // Execution context
PlanHash []byte // Deterministic plan hash/salt for value resolution context
StepPath string // Step path prefix for value resolution provenance
Telemetry *PlanTelemetry // Optional telemetry sink
TelemetryLevel TelemetryLevel // Telemetry level
}
ResolveConfig configures the resolution process.
type ResolveResult ¶
type ResolveResult struct {
Statements []*StatementIR // Pruned tree (only taken branches, nested blockers resolved)
DecoratorExprIDs map[string]string // Decorator key → exprID for display ID lookup
EnumMemberValues map[string]string // Enum member key (Type.Member) -> resolved string value
}
ResolveResult contains the resolved execution tree. The tree preserves structure for rich dry-run output:
- Blockers remain as nodes with Taken set and untaken branches pruned
- For-loops have Iterations populated with resolved values and deep-copied bodies
- Try/catch blocks are preserved as-is (runtime constructs)
func Resolve ¶
func Resolve(graph *ExecutionGraph, v *vault.Vault, session decorator.Session, config ResolveConfig) (*ResolveResult, error)
Resolve processes the execution graph and resolves all expressions. Returns a ResolveResult containing the pruned execution tree, or an error if resolution fails (undefined variables, decorator failures, etc.).
The returned tree preserves structure for rich dry-run output:
- Blockers remain as nodes with Taken set and untaken branches pruned (set to nil)
- For-loops have Iterations populated with resolved values and deep-copied bodies
- Try/catch blocks are preserved as-is (runtime constructs)
type Resolver ¶
type Resolver struct {
// contains filtered or unexported fields
}
Resolver processes an ExecutionGraph, resolving all expressions and determining which branches are taken. Uses a wave-based resolution model.
Scope Semantics: Metaprogramming vs Execution Blocks ¶
Opal has two kinds of blocks with different scoping rules:
**Language control blocks (if, for, when, fun)** - These are METAPROGRAMMING constructs that are evaluated at plan-time and "disappear" in the final plan. Variables declared inside these blocks LEAK to the outer scope because the block structure is flattened away.
var OUTER = "before"
if @var.COND {
var INNER = "inside" // Leaks to outer scope!
}
echo @var.INNER // Works if COND was true
After planning (if COND is true), this becomes:
var OUTER = "before" var INNER = "inside" // Flattened - no if block anymore echo @var.INNER
**Execution blocks (try/catch, @retry, @timeout, etc.)** - These are RUNTIME constructs that remain in the plan. Variables declared inside these blocks are ISOLATED and do NOT leak to the outer scope.
var counter = 0
try {
var counter = 5 // Local copy, doesn't affect outer
}
echo @var.counter // Still 0
This distinction exists because:
- Metaprogramming blocks resolve deterministically at plan-time
- Execution blocks have runtime behavior (retries, exceptions) that's non-deterministic
Blockers ¶
A "blocker" is a control flow construct (if/when/for) that cannot be evaluated until its condition or collection expression is resolved. We can't know which branch to take (if/when) or how many iterations to unroll (for) until the controlling expression has a concrete value.
Example blockers:
- `if @var.ENV == "prod"` - blocked until ENV is resolved
- `for item in @var.ITEMS` - blocked until ITEMS collection is resolved
- `when @env.REGION` - blocked until REGION is resolved
Sequential Processing with Flattening ¶
Because metaprogramming blocks leak variables, statements AFTER a blocker must wait until the blocker is evaluated and its taken branch is processed. This ensures variables declared in taken branches are visible to subsequent statements.
Processing order for: `var A; if COND { var B }; echo @var.B`
- Process `var A` - add to scope
- Hit blocker `if COND` - STOP, resolve COND
- Evaluate blocker - condition is true
- Queue: taken branch statements + remaining statements after blocker
- Process `var B` from taken branch - add to scope
- Process `echo @var.B` - B is now visible!
Wave-Based Resolution ¶
Resolution proceeds in waves, processing statements sequentially:
- Collect expressions until we hit a blocker
- Batch resolve all collected expressions (grouped by decorator type)
- Evaluate the blocker, determine taken branch
- Queue: taken branch + remaining statements after blocker
- Repeat until no more statements
For-Loop Unrolling ¶
For-loops are blockers because we can't unroll until the collection is resolved. Once resolved, unrolling produces flat statements with VarDecl injections:
for item in ["a", "b"] { echo @var.item }
Unrolls to:
VarDecl: item = "a" echo @var.item VarDecl: item = "b" echo @var.item
Each VarDecl rebinds the loop variable before its iteration's body statements. This ensures nested blockers (like `if @var.item == "b"`) see the correct value.
Branch Pruning ¶
Untaken branches are never traversed or resolved. This provides:
- Security: Secrets in untaken branches are never resolved or exposed
- Efficiency: No wasted API calls for unused decorator values
Key Principles ¶
- Sequential processing: Statements after blockers wait for taken branch
- Flattening semantics: Metaprogramming blocks disappear, variables leak
- Scope isolation: Execution blocks (try/catch, decorators) don't leak
- Batch-first: Collect expressions up to blocker, THEN batch resolve
- Branch pruning: Untaken branches are never resolved
type ScopeStack ¶
type ScopeStack struct {
// contains filtered or unexported fields
}
ScopeStack tracks variable scopes during IR building. Each scope maps variable names to their expression IDs.
func NewScopeStack ¶
func NewScopeStack() *ScopeStack
NewScopeStack creates a new scope stack with a root scope.
func (*ScopeStack) Clone ¶
func (s *ScopeStack) Clone() *ScopeStack
Clone returns a deep copy of the scope stack.
func (*ScopeStack) Define ¶
func (s *ScopeStack) Define(name, exprID string)
Define adds a variable to the current scope.
type SourceSpan ¶
type SourceSpan struct {
File string // Source file name (empty for inline/REPL)
Start int // Start position (byte offset)
End int // End position (byte offset)
}
SourceSpan identifies a range in source code for error messages.
type StatementIR ¶
type StatementIR struct {
Kind StatementKind
Span SourceSpan
CreatesScope bool // True for decorator blocks, try/catch
// Exactly one of these is set based on Kind
Command *CommandStmtIR // For StmtCommand
VarDecl *VarDeclIR // For StmtVarDecl
Blocker *BlockerIR // For StmtBlocker
Try *TryIR // For StmtTry
FunctionCall *FunctionCallStmtIR // For StmtFunctionCall
CallTrace *CallTraceStmtIR // For StmtCallTrace
}
StatementIR represents a statement in the execution graph.
func DeepCopyStatement ¶
func DeepCopyStatement(stmt *StatementIR) *StatementIR
DeepCopyStatement creates a deep copy of a single statement.
func DeepCopyStatements ¶
func DeepCopyStatements(stmts []*StatementIR) []*StatementIR
DeepCopyStatements creates a deep copy of a statement slice. Used by the Resolver to create independent copies for each loop iteration, allowing nested blockers to be resolved independently.
type StatementKind ¶
type StatementKind int
StatementKind identifies the type of statement.
const ( StmtCommand StatementKind = iota // Shell command or decorator invocation StmtVarDecl // Variable declaration StmtBlocker // Control flow (if/when/for) StmtTry // Try/catch/finally error handling StmtFunctionCall // Function call statement StmtCallTrace // Call provenance wrapper (display-only) )
type StructFieldIR ¶
StructFieldIR represents a single struct field.
type StructTypeIR ¶
type StructTypeIR struct {
Name string
Fields []StructFieldIR
Span SourceSpan
}
StructTypeIR represents a top-level user-defined struct declaration.
type TelemetryLevel ¶
type TelemetryLevel int
TelemetryLevel controls telemetry collection.
const ( TelemetryOff TelemetryLevel = iota TelemetryBasic TelemetryTiming )
type TryIR ¶
type TryIR struct {
TryBlock []*StatementIR // Statements in try block
CatchBlock []*StatementIR // Statements in catch block (optional)
FinallyBlock []*StatementIR // Statements in finally block (optional)
}
TryIR represents try/catch/finally error handling.
type ValueLookup ¶
ValueLookup is a function that looks up a value by name. Returns (value, true) if found, (nil, false) if not found. Used by EvaluateExpr to look up variable and decorator values.
type VarDeclIR ¶
type VarDeclIR struct {
Name string // Variable name (without @var. prefix)
Value *ExprIR // Value expression
ExprID string // Unique expression ID (set during IR building)
}
VarDeclIR represents a variable declaration.
type WhenArmIR ¶
type WhenArmIR struct {
Pattern *ExprIR // Pattern to match (literal, regex, range, else)
Body []*StatementIR // Statements to execute if pattern matches
}
WhenArmIR represents a single arm in a when statement.