semantic

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 22, 2025 License: Apache-2.0 Imports: 3 Imported by: 0

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

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

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:

  1. If name is a defined variable (user, built-in, or conditional) → Interpolation
  2. If name is an automatic variable → Error
  3. 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:

  1. Automatic variables are only valid in recipe/block scope
  2. Captures are only valid in the recipe that defines them
  3. 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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL