parser

package
v0.8.0 Latest Latest
Warning

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

Go to latest
Published: Jan 19, 2026 License: MIT Imports: 7 Imported by: 8

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

Examples

Constants

View Source
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

type Builder struct {
	LexerBuilder *lexer.Builder
	// contains filtered or unexported fields
}

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 NewBuilder(lb *lexer.Builder) *Builder

func (*Builder) Build

func (pb *Builder) Build(input string) *Parser

Build creates a new instance of the parser.

func (*Builder) Install

func (pb *Builder) Install(plugin func(*Builder)) *Builder

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

func (pb *Builder) WithSmartSemicolon(enabled bool) *Builder

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

func (pb *Builder) WithTolerantMode(enabled bool) *Builder

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

type Interceptor[T ast.Node] func(p *Parser, next func() T) T

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

func (p *Parser) AddError(message string)

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

func (p *Parser) AddErrorAtToken(message string, tok token.Token)

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

func (p *Parser) ExpectSemicolonASI() bool

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

func (p *Parser) ExpectToken(t token.Type) bool

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

func (p *Parser) IsInFunction() bool

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

func (p *Parser) ParseProgram() (*ast.Program, error)

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 (p *Parser) ParseStatement() ast.Statement

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.

type ParserError

type ParserError struct {
	Message string `json:"message"`
	Range   Range  `json:"range"`
	Code    string `json:"code,omitempty"`
}

type Range added in v0.5.0

type Range struct {
	Start token.Position `json:"start"`
	End   token.Position `json:"end"`
}

Jump to

Keyboard shortcuts

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