parser

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: 7 Imported by: 0

Documentation

Overview

Package parser implements syntactic analysis for Buildfiles.

The parser transforms a token stream from the lexer into an AST. It maintains scope context to validate directive placement and handles error recovery to collect multiple errors from a single parse.

Scope Stack

The parser tracks nested scopes during parsing:

  • ScopeGlobal: Top-level scope
  • ScopeEnvironment: Inside .environment: block
  • ScopeRecipe: Inside a target's recipe
  • ScopeBlock: Inside block: within a recipe

Directives are validated against their allowed scopes. For example, .shell: is valid at GLOBAL and RECIPE scopes, while .using: is only valid in ENVIRONMENT scope.

Error Handling

The parser uses two complementary error handling patterns:

Top-level parsing functions (ParseVariable, ParseTarget, ParseConditional, etc.) return *ParseError. This allows ParseBuildfile to catch errors and perform recovery via recoverToLevel0().

Value/content parsing functions (ParseValue, parseInterpolation, parseFunctionCall, etc.) use addError() internally and return partial results. This enables continued parsing despite malformed values.

This two-tier pattern supports error recovery: structural errors stop the current block and trigger recovery, while value-level errors are collected but don't halt parsing.

Error Recovery

On parse error, the parser:

  1. Records the error in the ParseErrors collection
  2. Skips to the next line at indentation level 0 (global scope)
  3. Continues parsing from there
  4. Stops after maxErrors (10) to avoid infinite loops

This enables partial analysis of broken files and provides comprehensive error feedback rather than stopping at the first error.

Parsing Buildfiles

Use ParseBuildfile() for complete buildfile parsing with error recovery:

lex := lexer.New("Buildfile", content)
p := parser.New(lex)
stmts, errs := p.ParseBuildfile()

For parsing individual constructs (useful in testing or tooling), use the specific Parse* methods: ParseVariable, ParseTarget, ParseEnvironment, ParseConditional, ParseInclude.

Package parser implements the parsing phase for Buildfile source code. It transforms a token stream from the lexer into an Abstract Syntax Tree (AST).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DirectiveNameForError

func DirectiveNameForError(tok lexer.TokenType) string

DirectiveNameForError returns the directive name for error messages.

func IsDirectiveValidAtScope

func IsDirectiveValidAtScope(directive lexer.TokenType, scope Scope) bool

IsDirectiveValidAtScope returns true if the directive is valid at the given scope.

func LookupKeyword

func LookupKeyword(literal string) lexer.TokenType

LookupKeyword is a helper that checks if a literal is a keyword. This is used to check for function names.

Types

type ParseError

type ParseError struct {
	Message  string
	Location lexer.SourceLocation
	Hint     string // Optional hint for fixing the error
}

ParseError represents an error encountered during parsing.

func NewScopeError

func NewScopeError(directive lexer.TokenType, found Scope, loc lexer.SourceLocation) *ParseError

NewScopeError creates a parse error for a directive used at an invalid scope.

func (*ParseError) Error

func (e *ParseError) Error() string

Error implements the error interface.

type ParseErrors

type ParseErrors struct {
	Errors []*ParseError
}

ParseErrors collects multiple parse errors.

func (*ParseErrors) Add

func (pe *ParseErrors) Add(err *ParseError)

Add appends an error to the collection.

func (*ParseErrors) Error

func (pe *ParseErrors) Error() string

Error implements the error interface.

func (*ParseErrors) HasErrors

func (pe *ParseErrors) HasErrors() bool

HasErrors returns true if there are any errors.

type Parser

type Parser struct {
	// contains filtered or unexported fields
}

Parser transforms a token stream into an AST.

func New

func New(l *lexer.Lexer) *Parser

New creates a new Parser for the given lexer.

func (*Parser) CurrentIndentLevel

func (p *Parser) CurrentIndentLevel() int

CurrentIndentLevel returns the expected indentation level for the current scope. Level 0 = global, Level 1 = environment/recipe, Level 2 = block.

func (*Parser) CurrentScope

func (p *Parser) CurrentScope() Scope

CurrentScope returns the current parsing scope.

func (*Parser) CurrentToken

func (p *Parser) CurrentToken() lexer.Token

CurrentToken returns the current token.

func (*Parser) EnterScope

func (p *Parser) EnterScope(scope Scope)

EnterScope pushes a new scope onto the stack.

func (*Parser) Errors

func (p *Parser) Errors() *ParseErrors

Errors returns the collected parse errors.

func (*Parser) ExitScope

func (p *Parser) ExitScope() Scope

ExitScope pops the current scope from the stack.

func (*Parser) HasErrors

func (p *Parser) HasErrors() bool

HasErrors returns true if any parse errors were encountered.

func (*Parser) IsConditionalLine

func (p *Parser) IsConditionalLine() bool

IsConditionalLine checks if the current token starts a conditional. Returns true for if, ifdef, or ifndef keywords.

func (*Parser) ParseBuildfile

func (p *Parser) ParseBuildfile() ([]ast.Statement, *ParseErrors)

ParseBuildfile parses a complete buildfile with error recovery. It collects multiple errors and attempts to continue parsing after each error. Returns a slice of successfully parsed statements and all collected errors.

func (*Parser) ParseConditional

func (p *Parser) ParseConditional() (*ast.Conditional, *ParseError)

ParseConditional parses a conditional block (if/elif/else/end or ifdef/ifndef/end). Grammar:

conditional = if_clause { elif_clause } [ else_clause ] "end" NEWLINE ;
if_clause = "if" condition NEWLINE { statement } ;
elif_clause = "elif" condition NEWLINE { statement } ;
else_clause = "else" NEWLINE { statement } ;
condition = interpolation "==" value | interpolation "!=" value ;
ifdef_clause = "ifdef" identifier NEWLINE { statement } "end" NEWLINE ;
ifndef_clause = "ifndef" identifier NEWLINE { statement } "end" NEWLINE ;

func (*Parser) ParseEnvironment

func (p *Parser) ParseEnvironment() (*ast.Environment, *ParseError)

ParseEnvironment parses an environment block. Grammar: environment_block = ".environment:" [ identifier ] NEWLINE

INDENT { env_directive NEWLINE } DEDENT ;

env_directive = ".using:" value | ".source:" value | ".args:" value | ".requires:" value ;

func (*Parser) ParseInclude

func (p *Parser) ParseInclude() (*ast.Directive, []ast.Statement, *ParseError)

ParseInclude parses a .include: directive and recursively parses the included file. Returns:

  • The directive AST node
  • The statements from the included file (already parsed)
  • Any error encountered

Grammar: ".include:" value NEWLINE

func (*Parser) ParseTarget

func (p *Parser) ParseTarget() (*ast.Target, *ParseError)

ParseTarget parses a target definition. Grammar: target_def = target_spec ":" dependency_list NEWLINE [ recipe ] ; Grammar: target_spec = phony_target | file_target ; Grammar: phony_target = "@" identifier ; Grammar: file_target = path_pattern ;

func (*Parser) ParseValue

func (p *Parser) ParseValue() *ast.Value

ParseValue is the exported method for parsing a value.

func (*Parser) ParseVariable

func (p *Parser) ParseVariable() (*ast.Variable, *ParseError)

ParseVariable parses a variable definition. Grammar: variable_def = [ "lazy" ] identifier "=" value NEWLINE ;

type Scope

type Scope int

Scope represents the current parsing context. Different directives are valid at different scopes.

const (
	// ScopeGlobal is the top-level scope for global directives, variables, targets.
	ScopeGlobal Scope = iota
	// ScopeEnvironment is inside an .environment: block.
	ScopeEnvironment
	// ScopeRecipe is inside a target's recipe section.
	ScopeRecipe
	// ScopeBlock is inside a block: within a recipe.
	ScopeBlock
)

func ValidScopesForDirective

func ValidScopesForDirective(directive lexer.TokenType) []Scope

ValidScopesForDirective returns the list of scopes where a directive is valid.

func (Scope) String

func (s Scope) String() string

String returns the string representation of the scope.

type ScopeStack

type ScopeStack struct {
	// contains filtered or unexported fields
}

ScopeStack tracks nested scopes during parsing. The parser uses this to validate directive placement.

func NewScopeStack

func NewScopeStack() *ScopeStack

NewScopeStack creates a new scope stack initialized at global scope.

func (*ScopeStack) Current

func (ss *ScopeStack) Current() Scope

Current returns the current (topmost) scope.

func (*ScopeStack) Depth

func (ss *ScopeStack) Depth() int

Depth returns the current nesting depth.

func (*ScopeStack) IsIn

func (ss *ScopeStack) IsIn(scope Scope) bool

IsIn returns true if the given scope is anywhere in the stack. This is useful for checking if we're "inside" a particular scope.

func (*ScopeStack) Pop

func (ss *ScopeStack) Pop() Scope

Pop removes and returns the topmost scope. If at global scope, returns ScopeGlobal without modifying the stack.

func (*ScopeStack) Push

func (ss *ScopeStack) Push(scope Scope)

Push adds a new scope to the stack.

func (*ScopeStack) Reset

func (ss *ScopeStack) Reset()

Reset clears the stack and returns to global scope.

Jump to

Keyboard shortcuts

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