Documentation
¶
Overview ¶
Package semantic provides semantic analysis for Buildfiles.
This file implements Pass 2: Capture Validation. It resolves BraceExpr nodes in target patterns to either:
- Capture: undefined name that will be matched at build time
- Interpolation: reference to a defined variable
It also validates:
- Automatic variables cannot be used as captures
- Captures in dependencies must also appear in the target pattern
Package semantic provides semantic analysis for Buildfiles.
Semantic analysis validates the AST produced by the parser and resolves context-sensitive constructs. It runs in four passes, each building on the results of previous passes.
Pass 1: Symbol Collection (collector.go) ¶
The Collect function walks the AST and populates a symbol table with all definitions:
- Variables (immediate and lazy)
- Targets (file, phony, directory, pattern)
- Environments (named and default)
- Conditional variables (tracked separately for runtime evaluation)
Duplicate definitions are detected and reported as errors.
Pass 2: Capture Validation (capture.go) ¶
The ValidateCaptures function resolves BraceExpr nodes in target patterns. For each {name} in a pattern:
- If name is a defined variable → Interpolation
- If name is an automatic variable → Error (runtime-only)
- Otherwise → Capture (pattern matching variable)
Captures in dependencies must be defined in the target pattern; a dependency cannot introduce a new capture.
Pass 3: Reference Validation (reference.go) ¶
The ValidateReferences function verifies that all variable references point to defined symbols:
- Automatic variables (target, deps, in, out, stem, etc.) are only valid inside recipe or block scope
- Capture variables are only valid inside their defining recipe
- Built-in variables (os, arch) are always valid
Pass 4: Dependency Graph (depgraph.go) ¶
The ValidateDependencies function builds the dependency graph and detects circular dependencies:
- Pattern targets are tracked separately (they define rules, not nodes)
- Unsatisfied dependencies (deps not defined as targets) are recorded but not errors—they may be source files or satisfiable by patterns
- Circular dependencies are detected using DFS-based cycle detection
Symbol Table (symbols.go) ¶
The SymbolTable tracks all defined symbols with O(1) lookup by name for variables and environments. Targets are stored in definition order for match priority. The table distinguishes:
- User-defined variables
- Automatic variables (target, deps, in, out, stem, target.dir, target.file)
- Built-in variables (os, arch)
Usage ¶
Typical usage runs all four passes in sequence:
// Parse
stmts, errs := parser.ParseBuildfile()
// Pass 1
collectResult := semantic.Collect(stmts)
// Pass 2
captureResult := semantic.ValidateCaptures(collectResult.SymbolTable)
// Pass 3
refResult := semantic.ValidateReferences(collectResult.SymbolTable, stmts,
semantic.WithCaptures(captureResult))
// Pass 4
depResult := semantic.ValidateDependencies(collectResult.SymbolTable.Targets)
Package semantic provides semantic analysis for Buildfiles.
This file defines all semantic error types for the build tool. These errors are generated during semantic analysis passes:
- Pass 1 (Symbol Collection): DuplicateDefinitionError
- Pass 2 (Capture Validation): AutomaticInPatternError, CaptureMismatchError
- Pass 3 (Reference Validation): UndefinedVariableError, AutomaticOutsideRecipeError
- Pass 4 (Dependency Graph): CircularDependencyError
Package semantic provides semantic analysis for Buildfiles.
This file implements Pass 3: Reference Validation. It validates that all variable references point to defined symbols:
- User-defined variables
- Built-in variables (os, arch)
- Automatic variables (target, deps, in, out, stem, target.dir, target.file) - only in recipe scope
- Captures - only in the recipe that defines them
Package semantic provides semantic analysis for Buildfiles.
Semantic analysis validates the AST produced by the parser and resolves context-sensitive constructs like captures vs interpolations in target patterns.
The semantic analyzer performs several passes:
- Pass 1: Symbol Collection - collect all definitions
- Pass 2: Capture Validation - resolve {name} in target patterns
- Pass 3: Reference Validation - verify all references
- Pass 4: Dependency Graph Validation - detect cycles
Index ¶
- func PatternString(p *ast.TargetPattern) string
- func SegmentsToString(segments []ast.PatternSegment) string
- type AutomaticInPatternError
- type AutomaticOutsideRecipeError
- type CaptureInfo
- type CaptureMismatchError
- type CaptureResult
- type CircularDependencyError
- type CollectResult
- type ConditionalVarDef
- type DependencyGraph
- type DependencyResult
- type DuplicateDefinitionError
- type InterpolationInfo
- type ReferenceOption
- type ReferenceResult
- type SymbolTable
- func (st *SymbolTable) AddConditionalVariable(def *ConditionalVarDef)
- func (st *SymbolTable) AddEnvironment(e *ast.Environment) error
- func (st *SymbolTable) AddTarget(t *ast.Target) error
- func (st *SymbolTable) AddVariable(v *ast.Variable) error
- func (st *SymbolTable) GetConditionalVarDefs(name string) []*ConditionalVarDef
- func (st *SymbolTable) IsAutomatic(name string) bool
- func (st *SymbolTable) IsBuiltin(name string) bool
- func (st *SymbolTable) IsConditionalVar(name string) bool
- func (st *SymbolTable) IsDefined(name string) bool
- func (st *SymbolTable) LookupVariable(name string) *ast.Variable
- type UndefinedVariableError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func PatternString ¶
func PatternString(p *ast.TargetPattern) string
PatternString converts a target pattern to a string representation for duplicate detection and display purposes. Note: Phony targets are stored without the @ prefix; use IsPhony flag to check.
func SegmentsToString ¶
func SegmentsToString(segments []ast.PatternSegment) string
SegmentsToString converts a slice of pattern segments to a string representation. This is the underlying implementation used by PatternString and for dependency formatting.
Types ¶
type AutomaticInPatternError ¶
type AutomaticInPatternError struct {
Name string // The automatic variable name
Location ast.SourceLocation // Location of the usage
}
AutomaticInPatternError is returned when an automatic variable is used in a target pattern. Automatic variables (target, deps, in, out, stem, target.dir, target.file) are only available during recipe execution and cannot be used as capture patterns.
Example error case:
build/{target}.o: src/main.c # Error: 'target' is automatic
func (*AutomaticInPatternError) Error ¶
func (e *AutomaticInPatternError) Error() string
type AutomaticOutsideRecipeError ¶
type AutomaticOutsideRecipeError struct {
Name string // The automatic variable name
Location ast.SourceLocation // Location of the reference
}
AutomaticOutsideRecipeError is returned when an automatic variable is used outside a recipe. Automatic variables are only computed during recipe execution and are not available in global variable definitions, directive values, or other non-recipe contexts.
Example error case:
output = {target} # Error: 'target' only valid in recipes
func (*AutomaticOutsideRecipeError) Error ¶
func (e *AutomaticOutsideRecipeError) Error() string
type CaptureInfo ¶
type CaptureInfo struct {
Names []string // Unique capture names in order of first appearance
}
CaptureInfo holds information about captures in a target pattern.
type CaptureMismatchError ¶
type CaptureMismatchError struct {
Name string // The capture name
InTarget bool // true if capture is in target but not deps (this is allowed)
Location ast.SourceLocation // Location of the problematic usage
TargetLoc ast.SourceLocation // Location of the target definition
}
CaptureMismatchError is returned when a capture appears in dependencies but not in the target. Captures must be defined in the target pattern before they can be used in dependencies.
Example error case:
build/app: src/{name}.c # Error: 'name' not defined in target
func (*CaptureMismatchError) Error ¶
func (e *CaptureMismatchError) Error() string
type CaptureResult ¶
type CaptureResult struct {
// Captures maps each target to its capture information.
// Only targets with captures are included.
Captures map[*ast.Target]*CaptureInfo
// Interpolations maps each target to its interpolation information.
// Only targets with interpolations in patterns are included.
Interpolations map[*ast.Target]*InterpolationInfo
// Errors holds any validation errors encountered.
Errors []error
}
CaptureResult holds the result of capture validation (Pass 2).
func ValidateCaptures ¶
func ValidateCaptures(st *SymbolTable) *CaptureResult
ValidateCaptures performs Pass 2: Capture Validation on the symbol table. It resolves BraceExpr nodes in target patterns and validates capture usage.
Resolution rules:
- If name is a defined variable (user, built-in, or conditional) → Interpolation
- If name is an automatic variable → Error
- Otherwise → Capture
Consistency rules:
- Captures in dependencies must also appear in the target pattern
- Captures in target can have literal dependencies (no matching capture required)
func (*CaptureResult) HasErrors ¶
func (r *CaptureResult) HasErrors() bool
HasErrors returns true if any validation errors were found.
type CircularDependencyError ¶
type CircularDependencyError struct {
// Cycle contains the targets in the cycle, e.g., ["a", "b", "c", "a"]
// The first and last elements are the same, showing where the cycle closes.
Cycle []string
}
CircularDependencyError is returned when a circular dependency is detected in the target dependency graph.
Example error case:
a: b b: a # Error: circular dependency a -> b -> a
func (*CircularDependencyError) Error ¶
func (e *CircularDependencyError) Error() string
type CollectResult ¶
type CollectResult struct {
SymbolTable *SymbolTable
Errors []error
}
CollectResult holds the result of symbol collection. This is useful for returning both the symbol table and errors.
func Collect ¶
func Collect(stmts []ast.Statement) *CollectResult
Collect performs Pass 1: Symbol Collection on the given statements. It walks the AST and populates a symbol table with all definitions. Returns a CollectResult containing the symbol table and any errors (duplicate definitions).
Collect handles:
- Variable definitions (immediate and lazy)
- Target definitions (file, phony, directory, pattern)
- Environment definitions (named and default)
- Variables inside conditionals (tracked separately)
func (*CollectResult) HasErrors ¶
func (r *CollectResult) HasErrors() bool
HasErrors returns true if any errors were encountered during collection.
type ConditionalVarDef ¶
type ConditionalVarDef struct {
Variable *ast.Variable // The variable definition
Conditional *ast.Conditional // The containing conditional
BranchType string // "if", "elif", or "else"
BranchIndex int // For elif, the index (0-based); -1 for if/else
}
ConditionalVarDef represents a variable definition within a conditional. It tracks the conditional context so runtime can evaluate the condition.
type DependencyGraph ¶
type DependencyGraph struct {
// Nodes contains all target names in the graph.
Nodes map[string]bool
// Edges maps each target to its dependencies.
// If A depends on B, then Edges[A] contains B.
Edges map[string][]string
}
DependencyGraph represents the dependency relationships between targets.
func NewDependencyGraph ¶
func NewDependencyGraph() *DependencyGraph
NewDependencyGraph creates an empty dependency graph.
func (*DependencyGraph) AddEdge ¶
func (g *DependencyGraph) AddEdge(target, dep string)
AddEdge adds a dependency edge from target to dep.
func (*DependencyGraph) AddNode ¶
func (g *DependencyGraph) AddNode(name string)
AddNode adds a target node to the graph.
type DependencyResult ¶
type DependencyResult struct {
// Graph is the constructed dependency graph.
Graph *DependencyGraph
// PatternTargets contains targets that have pattern captures.
// These define rules rather than concrete targets.
PatternTargets []*ast.Target
// UnsatisfiedDeps maps target names to their unsatisfied dependencies.
// A dependency is unsatisfied if it's not defined as a target
// (it may be a source file or need to be matched by a pattern).
UnsatisfiedDeps map[string][]string
// Errors contains validation errors (e.g., circular dependencies).
Errors []error
}
DependencyResult contains the results of dependency validation.
func ValidateDependencies ¶
func ValidateDependencies(targets []*ast.Target) *DependencyResult
ValidateDependencies performs Pass 4 of semantic analysis: builds the dependency graph and detects cycles.
func (*DependencyResult) HasErrors ¶
func (r *DependencyResult) HasErrors() bool
HasErrors returns true if there are any validation errors.
type DuplicateDefinitionError ¶
type DuplicateDefinitionError struct {
Kind string // "variable", "target", or "environment"
Name string // The name that was duplicated
First ast.SourceLocation // Location of first definition
Second ast.SourceLocation // Location of duplicate definition
}
DuplicateDefinitionError is returned when a symbol (variable, target, or environment) is defined multiple times in the same Buildfile or included files.
func (*DuplicateDefinitionError) Error ¶
func (e *DuplicateDefinitionError) Error() string
type InterpolationInfo ¶
type InterpolationInfo struct {
Names []string // Variable names used as interpolations
}
InterpolationInfo holds information about interpolations in a target pattern.
type ReferenceOption ¶
type ReferenceOption func(*referenceValidator)
ReferenceOption configures reference validation.
func WithCaptures ¶
func WithCaptures(captureResult *CaptureResult) ReferenceOption
WithCaptures provides capture information from Pass 2 for reference validation. This allows captures to be recognized as valid references in their defining recipe.
type ReferenceResult ¶
type ReferenceResult struct {
// Errors holds any validation errors encountered.
Errors []error
}
ReferenceResult holds the result of reference validation (Pass 3).
func ValidateReferences ¶
func ValidateReferences(st *SymbolTable, stmts []ast.Statement, opts ...ReferenceOption) *ReferenceResult
ValidateReferences performs Pass 3: Reference Validation. It validates that all variable references in the AST are defined.
Validation rules:
- Automatic variables are only valid in recipe/block scope
- Captures are only valid in the recipe that defines them
- All other references must be defined (user, conditional, or built-in)
The opts parameter allows providing capture information from Pass 2.
func (*ReferenceResult) HasErrors ¶
func (r *ReferenceResult) HasErrors() bool
HasErrors returns true if any validation errors were found.
type SymbolTable ¶
type SymbolTable struct {
// Variables maps variable names to their definitions.
// For unconditional variables, this is the single definition.
// For conditional variables, this is the first definition encountered.
Variables map[string]*ast.Variable
// ConditionalVars tracks variables that are defined within conditionals.
// Maps variable name to all conditionals that define it.
// This allows runtime to evaluate conditions and pick the right value.
ConditionalVars map[string][]*ConditionalVarDef
// Targets holds all target definitions.
// We use a slice because pattern targets may overlap and we need
// to preserve definition order.
Targets []*ast.Target
// Environments maps environment names to their definitions.
// The empty string key is used for the default (unnamed) environment.
Environments map[string]*ast.Environment
// contains filtered or unexported fields
}
SymbolTable holds all defined symbols in a Buildfile. It tracks user-defined variables, targets, and environments, as well as automatic and built-in variables.
func NewSymbolTable ¶
func NewSymbolTable() *SymbolTable
NewSymbolTable creates an initialized symbol table with automatic and built-in variables populated.
func (*SymbolTable) AddConditionalVariable ¶
func (st *SymbolTable) AddConditionalVariable(def *ConditionalVarDef)
AddConditionalVariable adds a variable definition that appears within a conditional. Unlike AddVariable, this allows multiple definitions for the same variable name (one per conditional branch), since only one branch will execute at runtime.
func (*SymbolTable) AddEnvironment ¶
func (st *SymbolTable) AddEnvironment(e *ast.Environment) error
AddEnvironment adds an environment definition to the symbol table. Returns an error if an environment with the same name already exists.
func (*SymbolTable) AddTarget ¶
func (st *SymbolTable) AddTarget(t *ast.Target) error
AddTarget adds a target definition to the symbol table. Returns an error if an exact duplicate pattern already exists.
func (*SymbolTable) AddVariable ¶
func (st *SymbolTable) AddVariable(v *ast.Variable) error
AddVariable adds a variable definition to the symbol table. Returns an error if a variable with the same name already exists.
func (*SymbolTable) GetConditionalVarDefs ¶
func (st *SymbolTable) GetConditionalVarDefs(name string) []*ConditionalVarDef
GetConditionalVarDefs returns all conditional definitions for a variable.
func (*SymbolTable) IsAutomatic ¶
func (st *SymbolTable) IsAutomatic(name string) bool
IsAutomatic returns true if the name is an automatic variable.
func (*SymbolTable) IsBuiltin ¶
func (st *SymbolTable) IsBuiltin(name string) bool
IsBuiltin returns true if the name is a built-in variable.
func (*SymbolTable) IsConditionalVar ¶
func (st *SymbolTable) IsConditionalVar(name string) bool
IsConditionalVar returns true if the variable has conditional definitions.
func (*SymbolTable) IsDefined ¶
func (st *SymbolTable) IsDefined(name string) bool
IsDefined returns true if the name is defined as a user variable, conditional variable, automatic variable, or built-in variable.
func (*SymbolTable) LookupVariable ¶
func (st *SymbolTable) LookupVariable(name string) *ast.Variable
LookupVariable returns the variable with the given name, or nil if not found.
type UndefinedVariableError ¶
type UndefinedVariableError struct {
Name string // The undefined variable name
Location ast.SourceLocation // Location of the reference
}
UndefinedVariableError is returned when a reference to an undefined variable is found. All variable references must point to:
- A user-defined variable (immediate or lazy)
- A built-in variable (os, arch)
- An automatic variable (only in recipe/block scope)
- A capture variable (only in the defining recipe)
Example error case:
output = {undefined_var} # Error: 'undefined_var' not defined
func (*UndefinedVariableError) Error ¶
func (e *UndefinedVariableError) Error() string