Documentation
¶
Overview ¶
Package parser provides syntax analysis functionality for the XJS language. It builds an Abstract Syntax Tree (AST) from tokens provided by the lexer.
Example (Combined) ¶
input := `
const circleArea = PI * r^2
if (typeof radius == 'string') {
let randomRadius = RANDOM * 10
}`
lb := lexer.NewBuilder()
pb := NewBuilder(lb)
// registers PI keyword
piType := lb.RegisterTokenType("PI")
lb.UseTokenInterceptor(func(l *lexer.Lexer, next func() token.Token) token.Token {
ret := next()
if ret.Type == token.IDENT && ret.Literal == "PI" {
ret.Type = piType
}
return ret
})
// regists infix `^`
powType := lb.RegisterTokenType("pow")
lb.UseTokenInterceptor(func(l *lexer.Lexer, next func() token.Token) token.Token {
if l.CurrentChar == '^' {
l.ReadChar() // consume ^
pos := token.Position{Line: l.Line, Column: l.Column}
return token.Token{Type: powType, Literal: "^", Start: pos, End: pos}
}
return next()
})
// registers prefix `typeof`
typeofType := lb.RegisterTokenType("typeof")
lb.UseTokenInterceptor(func(l *lexer.Lexer, next func() token.Token) token.Token {
ret := next()
if ret.Type == token.IDENT && ret.Literal == "typeof" {
ret.Type = typeofType
}
return ret
})
// combines all previous examples!
pb.UseStatementInterceptor(func(p *Parser, next func() ast.Statement) ast.Statement {
if p.CurrentToken.Type == token.IDENT && p.CurrentToken.Literal == "const" {
stmt := &ConstStatement{Token: p.CurrentToken}
p.NextToken()
stmt.Name = &ast.Identifier{Token: p.CurrentToken, Value: p.CurrentToken.Literal}
if !p.ExpectToken(token.ASSIGN) {
return nil
}
p.NextToken()
stmt.Value = p.ParseExpression()
return stmt
}
return next()
})
_ = pb.RegisterPrefixOperator(typeofType, func(tok token.Token, right func() ast.Expression) ast.Expression {
return &TypeofExpression{Token: tok, Right: right()}
})
_ = pb.RegisterInfixOperator(powType, PRODUCT+1, func(tok token.Token, left ast.Expression, right func() ast.Expression) ast.Expression {
return &PowExpression{Token: tok, Left: left, Right: right()}
})
_ = pb.RegisterPrefixOperator(piType, func(tok token.Token, right func() ast.Expression) ast.Expression {
return &PiLiteral{Token: tok}
})
pb.UseExpressionInterceptor(func(p *Parser, next func() ast.Expression) ast.Expression {
if p.CurrentToken.Type == token.IDENT && p.CurrentToken.Literal == "RANDOM" {
return p.ParseRemainingExpression(&RandomExpression{Token: p.CurrentToken})
}
return next()
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: const circleArea=Math.PI*Math.pow(r,2)if((typeof radius)=="string"){let randomRadius=Math.random()*10;}
Example (ExpressionParser) ¶
input := "let randomValue = RANDOM + 10"
lb := lexer.NewBuilder()
pb := NewBuilder(lb)
// intercepts expression parsing to handle RANDOM as a special expression!
pb.UseExpressionInterceptor(func(p *Parser, next func() ast.Expression) ast.Expression {
if p.CurrentToken.Type == token.IDENT && p.CurrentToken.Literal == "RANDOM" {
return p.ParseRemainingExpression(&RandomExpression{Token: p.CurrentToken})
}
return next()
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: let randomValue=Math.random()+10;
Example (InfixOperator) ¶
input := "let squareArea = r^2"
lb := lexer.NewBuilder()
pb := NewBuilder(lb)
powType := lb.RegisterTokenType("pow")
lb.UseTokenInterceptor(func(l *lexer.Lexer, next func() token.Token) token.Token {
if l.CurrentChar == '^' {
l.ReadChar()
pos := token.Position{Line: l.Line, Column: l.Column}
return token.Token{Type: powType, Literal: "^", Start: pos, End: pos}
}
return next()
})
// adds support for the ^ operator!
_ = pb.RegisterInfixOperator(powType, PRODUCT+1, func(tok token.Token, left ast.Expression, right func() ast.Expression) ast.Expression {
return &PowExpression{
Token: tok,
Left: left,
Right: right(),
}
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: let squareArea=Math.pow(r,2);
Example (Operand) ¶
input := "let area = PI * r * r"
lb := lexer.NewBuilder()
pb := NewBuilder(lb)
// registers PI keyword
piType := lb.RegisterTokenType("PI")
lb.UseTokenInterceptor(func(l *lexer.Lexer, next func() token.Token) token.Token {
ret := next()
if ret.Type == token.IDENT && ret.Literal == "PI" {
ret.Type = piType
}
return ret
})
// adds support for the PI constant!
_ = pb.RegisterPrefixOperator(piType, func(tok token.Token, right func() ast.Expression) ast.Expression {
return &PiLiteral{Token: tok}
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: let area=Math.PI*r*r;
Example (PostfixOperator) ¶
input := "let result = 5! + 2!"
lb := lexer.NewBuilder()
pb := NewBuilder(lb)
// adds support for the ! postfix operator factorial
_ = pb.RegisterPostfixOperator(token.NOT, func(tok token.Token, left ast.Expression) ast.Expression {
return &FactorialExpression{
Token: tok,
Left: left,
}
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: let result=factorial(5)+factorial(2);
Example (PrefixOperator) ¶
input := "if (typeof x == 'string') { console.log('x is a string') }"
lb := lexer.NewBuilder()
// registers typeof keyword
typeofType := lb.RegisterTokenType("typeof")
lb.UseTokenInterceptor(func(l *lexer.Lexer, next func() token.Token) token.Token {
ret := next()
if ret.Type == token.IDENT && ret.Literal == "typeof" {
ret.Type = typeofType
}
return ret
})
// adds support for the typeof keyword!
pb := NewBuilder(lb)
_ = pb.RegisterPrefixOperator(typeofType, func(tok token.Token, right func() ast.Expression) ast.Expression {
return &TypeofExpression{
Token: tok,
Right: right(),
}
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: if((typeof x)=="string"){console.log("x is a string");}
Example (Statement) ¶
input := "const x = 42"
lb := lexer.NewBuilder()
pb := NewBuilder(lb)
// adds support for the `const` keyword!
pb.UseStatementInterceptor(func(p *Parser, next func() ast.Statement) ast.Statement {
if p.CurrentToken.Type == token.IDENT && p.CurrentToken.Literal == "const" {
stmt := &ConstStatement{Token: p.CurrentToken}
p.NextToken() // moves to identifier token
stmt.Name = &ast.Identifier{Token: p.CurrentToken, Value: p.CurrentToken.Literal}
if !p.ExpectToken(token.ASSIGN) { // expects "="
return nil
}
p.NextToken() // moves to value expression
stmt.Value = p.ParseExpression()
return stmt
}
return next() // otherwise, next!
})
p := pb.Build(input)
program, err := p.ParseProgram()
if err != nil {
panic(err)
}
result := compiler.New().Compile(program)
fmt.Println(result.Code)
Output: const x=42
Index ¶
- Constants
- type Builder
- func (pb *Builder) Build(input string) *Parser
- func (pb *Builder) Install(plugin func(*Builder)) *Builder
- func (pb *Builder) RegisterInfixOperator(tokenType token.Type, precedence int, ...) error
- func (pb *Builder) RegisterPostfixOperator(tokenType token.Type, ...) error
- func (pb *Builder) RegisterPrefixOperator(tokenType token.Type, ...) error
- func (pb *Builder) UseExpressionInterceptor(interceptor Interceptor[ast.Expression]) *Builder
- func (pb *Builder) UseStatementInterceptor(interceptor Interceptor[ast.Statement]) *Builder
- func (pb *Builder) WithSmartSemicolon(enabled bool) *Builder
- func (pb *Builder) WithTolerantMode(enabled bool) *Builder
- type ContextType
- type Interceptor
- type Parser
- func (p *Parser) AddError(message string)
- func (p *Parser) AddErrorAtToken(message string, tok token.Token)
- func (p *Parser) CurrentContext() ContextType
- func (p *Parser) Errors() []ParserError
- func (p *Parser) ExpectSemicolonASI() bool
- func (p *Parser) ExpectToken(t token.Type) bool
- func (p *Parser) IsInFunction() bool
- func (p *Parser) NextToken()
- func (p *Parser) ParseArrayLiteral() ast.Expression
- func (p *Parser) ParseAssignmentExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseBinaryExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseBlockStatement() *ast.BlockStatement
- func (p *Parser) ParseBooleanLiteral() ast.Expression
- func (p *Parser) ParseCallExpression(fn ast.Expression) ast.Expression
- func (p *Parser) ParseCompoundAssignmentExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseComputedMemberExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseExpression() ast.Expression
- func (p *Parser) ParseExpressionList(end token.Type) []ast.Expression
- func (p *Parser) ParseExpressionStatement() *ast.ExpressionStatement
- func (p *Parser) ParseExpressionWithPrecedence(precedence int) ast.Expression
- func (p *Parser) ParseFloatLiteral() ast.Expression
- func (p *Parser) ParseForStatement() *ast.ForStatement
- func (p *Parser) ParseFunctionExpression() ast.Expression
- func (p *Parser) ParseFunctionParameters() []*ast.Identifier
- func (p *Parser) ParseFunctionStatement() *ast.FunctionDeclaration
- func (p *Parser) ParseGroupedExpression() ast.Expression
- func (p *Parser) ParseIdentifier() ast.Expression
- func (p *Parser) ParseIfStatement() *ast.IfStatement
- func (p *Parser) ParseInfixExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseIntegerLiteral() ast.Expression
- func (p *Parser) ParseLetExpression() ast.Expression
- func (p *Parser) ParseLetStatement() *ast.LetStatement
- func (p *Parser) ParseMemberExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseMultiStringLiteral() ast.Expression
- func (p *Parser) ParseNullLiteral() ast.Expression
- func (p *Parser) ParseObjectLiteral() ast.Expression
- func (p *Parser) ParsePostfixExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParsePrefixExpression() ast.Expression
- func (p *Parser) ParseProgram() (*ast.Program, error)
- func (p *Parser) ParseRemainingExpression(left ast.Expression) ast.Expression
- func (p *Parser) ParseRemainingExpressionWithPrecedence(left ast.Expression, precedence int) ast.Expression
- func (p *Parser) ParseReturnStatement() *ast.ReturnStatement
- func (p *Parser) ParseStatement() ast.Statement
- func (p *Parser) ParseStringLiteral() ast.Expression
- func (p *Parser) ParseUnaryExpression() ast.Expression
- func (p *Parser) ParseWhileStatement() *ast.WhileStatement
- func (p *Parser) PopContext()
- func (p *Parser) PushContext(ctx ContextType)
- type ParserError
- type Range
Examples ¶
Constants ¶
const ( LOWEST int ASSIGNMENT // = LOGICAL_OR // || LOGICAL_AND // && EQUALITY // == != COMPARISON // > < >= <= SUM // + PRODUCT // * / % UNARY // -x !x ++x --x POSTFIX // x++ x-- CALL // myFunction(X) MEMBER // obj.prop obj[prop] )
Operator precedence levels
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
Builder provides a fluent interface for constructing a Parser with various middleware and transformations. It allows composition of lexer options, statement interceptors, expression interceptors, and program transformers.
func NewBuilder ¶
func (*Builder) Install ¶
Install installs a plugin, allowing to enhance the language by modifying or adding new operators, statements, and expressions.
func (*Builder) RegisterInfixOperator ¶
func (pb *Builder) RegisterInfixOperator(tokenType token.Type, precedence int, createExpr func(tok token.Token, left ast.Expression, right func() ast.Expression) ast.Expression) error
RegisterInfixOperator allows incorporating new infix operators (for example, `^` for power).
func (*Builder) RegisterPostfixOperator ¶
func (pb *Builder) RegisterPostfixOperator(tokenType token.Type, createExpr func(tok token.Token, left ast.Expression) ast.Expression) error
RegisterPostfixOperator allows registering new postfix operators.
func (*Builder) RegisterPrefixOperator ¶
func (pb *Builder) RegisterPrefixOperator(tokenType token.Type, createExpr func(tok token.Token, right func() ast.Expression) ast.Expression) error
RegisterPrefixOperator allows registering new prefix operators (for example, `typeof`).
func (*Builder) UseExpressionInterceptor ¶
func (pb *Builder) UseExpressionInterceptor(interceptor Interceptor[ast.Expression]) *Builder
UseExpressionInterceptor intercepts the parsing flow for expressions, allowing you to modify or add custom expressions.
func (*Builder) UseStatementInterceptor ¶
func (pb *Builder) UseStatementInterceptor(interceptor Interceptor[ast.Statement]) *Builder
UseStatementInterceptor intercepts the parsing flow for statements, allowing you to modify or add custom statements.
func (*Builder) WithSmartSemicolon ¶ added in v0.4.0
WithSmartSemicolon enables smart semicolon insertion to prevent common JavaScript pitfalls related to Automatic Semicolon Insertion (ASI). When enabled, the parser will prevent LPAREN '(' and LBRACKET '[' tokens after a newline from continuing the previous expression.
This prevents errors like:
// Without SmartSemicolon (standard JavaScript behavior):
console.log('first')
(function() {})() // Error: tries to call console.log result as function
// With SmartSemicolon enabled:
console.log('first')
(function() {})() // OK: treats IIFE as separate statement
Reference: https://eslint.org/docs/latest/rules/no-unexpected-multiline
Example:
parser := parser.NewBuilder(lexer.NewBuilder()).
WithSmartSemicolon(true).
Build(code)
func (*Builder) WithTolerantMode ¶
WithTolerantMode enables tolerant parsing mode, which continues parsing even on syntax errors. This is useful for language servers, formatters, and analysis tools that need to work with incomplete or invalid code. In tolerant mode, the parser will not stop on missing semicolons or other recoverable syntax errors.
Example:
parser := parser.NewBuilder(lexer.NewBuilder()).
WithTolerantMode(true).
Build(code)
type ContextType ¶
type ContextType int
ContextType represents different parsing contexts that help track the current scope during parsing for proper semantic analysis.
const ( // GlobalContext represents the top-level global scope GlobalContext ContextType = iota // FunctionContext represents parsing inside a function body FunctionContext // BlockContext represents parsing inside a block statement BlockContext )
type Interceptor ¶
Interceptor is a generic function type that allows middleware-style interception of parsing operations. It receives the parser and a next function to call the original parsing logic, enabling extension and modification of parsing behavior.
type Parser ¶
type Parser struct {
// CurrentToken is the token currently being processed
CurrentToken token.Token
// PeekToken is the next token in the stream (lookahead)
PeekToken token.Token
// contains filtered or unexported fields
}
Parser is the main structure responsible for syntactic analysis of XJS source code. It implements a Pratt parser (top-down operator precedence parser) that converts a stream of tokens from the lexer into an Abstract Syntax Tree (AST).
The parser supports middleware-style extensions through interceptors and transformers, allowing dynamic modification of parsing behavior without changing the core parser logic.
func (*Parser) AddError ¶
AddError creates and adds a new parsing error to the parser's error collection. It captures the current token's position information and creates a structured ParserError with the provided message.
func (*Parser) AddErrorAtToken ¶ added in v0.5.0
AddErrorAtToken creates and adds a new parsing error at a specific token's position. This method uses the token's Start and End positions to create a range that accurately represents the full extent of the token causing the error.
func (*Parser) CurrentContext ¶
func (p *Parser) CurrentContext() ContextType
CurrentContext returns the context at the top of the stack.
func (*Parser) Errors ¶
func (p *Parser) Errors() []ParserError
Errors returns a copy of all parsing errors encountered during parsing. This allows external code to inspect and handle parsing errors appropriately.
func (*Parser) ExpectSemicolonASI ¶
ExpectSemicolonASI expects either an explicit semicolon or ASI conditions. In tolerant mode, always returns true to continue parsing even on syntax errors. Returns true if a semicolon (explicit or virtual) is present, false otherwise.
func (*Parser) ExpectToken ¶
ExpectToken checks if the next token (PeekToken) matches the expected type. If it matches, the parser advances to that token and returns true. If it doesn't match, it adds a parsing error and returns false.
func (*Parser) IsInFunction ¶
IsInFunction checks if there's a FunctionContext anywhere in the context stack.
func (*Parser) NextToken ¶
func (p *Parser) NextToken()
NextToken advances the parser to the next token in the stream. It moves PeekToken to CurrentToken and reads a new token from the lexer into PeekToken. This maintains the one-token lookahead that enables efficient parsing decisions.
func (*Parser) ParseArrayLiteral ¶
func (p *Parser) ParseArrayLiteral() ast.Expression
func (*Parser) ParseAssignmentExpression ¶
func (p *Parser) ParseAssignmentExpression(left ast.Expression) ast.Expression
func (*Parser) ParseBinaryExpression ¶
func (p *Parser) ParseBinaryExpression(left ast.Expression) ast.Expression
func (*Parser) ParseBlockStatement ¶
func (p *Parser) ParseBlockStatement() *ast.BlockStatement
func (*Parser) ParseBooleanLiteral ¶
func (p *Parser) ParseBooleanLiteral() ast.Expression
func (*Parser) ParseCallExpression ¶
func (p *Parser) ParseCallExpression(fn ast.Expression) ast.Expression
func (*Parser) ParseCompoundAssignmentExpression ¶
func (p *Parser) ParseCompoundAssignmentExpression(left ast.Expression) ast.Expression
func (*Parser) ParseComputedMemberExpression ¶
func (p *Parser) ParseComputedMemberExpression(left ast.Expression) ast.Expression
func (*Parser) ParseExpression ¶
func (p *Parser) ParseExpression() ast.Expression
func (*Parser) ParseExpressionList ¶
func (p *Parser) ParseExpressionList(end token.Type) []ast.Expression
func (*Parser) ParseExpressionStatement ¶
func (p *Parser) ParseExpressionStatement() *ast.ExpressionStatement
func (*Parser) ParseExpressionWithPrecedence ¶
func (p *Parser) ParseExpressionWithPrecedence(precedence int) ast.Expression
func (*Parser) ParseFloatLiteral ¶
func (p *Parser) ParseFloatLiteral() ast.Expression
func (*Parser) ParseForStatement ¶
func (p *Parser) ParseForStatement() *ast.ForStatement
func (*Parser) ParseFunctionExpression ¶
func (p *Parser) ParseFunctionExpression() ast.Expression
func (*Parser) ParseFunctionParameters ¶
func (p *Parser) ParseFunctionParameters() []*ast.Identifier
func (*Parser) ParseFunctionStatement ¶
func (p *Parser) ParseFunctionStatement() *ast.FunctionDeclaration
func (*Parser) ParseGroupedExpression ¶
func (p *Parser) ParseGroupedExpression() ast.Expression
func (*Parser) ParseIdentifier ¶
func (p *Parser) ParseIdentifier() ast.Expression
func (*Parser) ParseIfStatement ¶
func (p *Parser) ParseIfStatement() *ast.IfStatement
func (*Parser) ParseInfixExpression ¶
func (p *Parser) ParseInfixExpression(left ast.Expression) ast.Expression
func (*Parser) ParseIntegerLiteral ¶
func (p *Parser) ParseIntegerLiteral() ast.Expression
func (*Parser) ParseLetExpression ¶ added in v0.8.0
func (p *Parser) ParseLetExpression() ast.Expression
ParseLetExpression parses a let expression (used in for loop initializers). Unlike ParseLetStatement, it returns an Expression and doesn't expect a semicolon.
func (*Parser) ParseLetStatement ¶
func (p *Parser) ParseLetStatement() *ast.LetStatement
func (*Parser) ParseMemberExpression ¶
func (p *Parser) ParseMemberExpression(left ast.Expression) ast.Expression
func (*Parser) ParseMultiStringLiteral ¶
func (p *Parser) ParseMultiStringLiteral() ast.Expression
func (*Parser) ParseNullLiteral ¶
func (p *Parser) ParseNullLiteral() ast.Expression
func (*Parser) ParseObjectLiteral ¶
func (p *Parser) ParseObjectLiteral() ast.Expression
func (*Parser) ParsePostfixExpression ¶
func (p *Parser) ParsePostfixExpression(left ast.Expression) ast.Expression
func (*Parser) ParsePrefixExpression ¶
func (p *Parser) ParsePrefixExpression() ast.Expression
func (*Parser) ParseProgram ¶
ParseProgram parses the entire source code and returns the resulting AST Program. It continuously parses statements until EOF is reached, collecting any parsing errors. After parsing, it applies any registered program transformers to the final AST.
func (*Parser) ParseRemainingExpression ¶
func (p *Parser) ParseRemainingExpression(left ast.Expression) ast.Expression
func (*Parser) ParseRemainingExpressionWithPrecedence ¶
func (p *Parser) ParseRemainingExpressionWithPrecedence(left ast.Expression, precedence int) ast.Expression
func (*Parser) ParseReturnStatement ¶
func (p *Parser) ParseReturnStatement() *ast.ReturnStatement
func (*Parser) ParseStatement ¶
func (*Parser) ParseStringLiteral ¶
func (p *Parser) ParseStringLiteral() ast.Expression
func (*Parser) ParseUnaryExpression ¶
func (p *Parser) ParseUnaryExpression() ast.Expression
func (*Parser) ParseWhileStatement ¶
func (p *Parser) ParseWhileStatement() *ast.WhileStatement
func (*Parser) PopContext ¶
func (p *Parser) PopContext()
PopContext removes the top context from the context stack.
func (*Parser) PushContext ¶
func (p *Parser) PushContext(ctx ContextType)
PushContext adds a new context to the top of the context stack.