Files
zzg a22c99344e
ci / test (push) Canceled after 0s
initial commit
2026-09-25 22:12:55 -07:00

194 lines
4.5 KiB
Go

// Package parser builds an expression tree from tokens.
package parser
import (
"fmt"
"strconv"
"github.com/zg/asm-calc/lexer"
)
// Node is an expression tree node.
type Node interface{ node() }
// Number is an integer literal.
type Number struct{ Value int64 }
// Variable is a named value.
type Variable struct{ Name string }
// BinaryOp is an infix operation.
type BinaryOp struct {
Op lexer.TokenType
Left, Right Node
}
// UnaryOp is a prefix operation (only negation).
type UnaryOp struct {
Op lexer.TokenType
Operand Node
}
// Assignment binds a name to an expression.
type Assignment struct {
Name string
Value Node
}
func (*Number) node() {}
func (*Variable) node() {}
func (*BinaryOp) node() {}
func (*UnaryOp) node() {}
func (*Assignment) node() {}
// Parse builds a tree from input.
func Parse(input string) (Node, error) {
tokens, err := lexer.Lex(input)
if err != nil {
return nil, err
}
p := &parser{tokens: tokens}
node, err := p.parseStatement()
if err != nil {
return nil, err
}
if p.cur().Type != lexer.EOF {
return nil, fmt.Errorf("column %d: unexpected %s", p.cur().Column, p.cur().Type)
}
return node, nil
}
type parser struct {
tokens []lexer.Token
pos int
}
func (p *parser) cur() lexer.Token { return p.tokens[p.pos] }
func (p *parser) peek() lexer.Token {
if p.pos+1 < len(p.tokens) {
return p.tokens[p.pos+1]
}
return p.tokens[len(p.tokens)-1]
}
func (p *parser) next() lexer.Token { t := p.tokens[p.pos]; p.pos++; return t }
func (p *parser) parseStatement() (Node, error) {
// An assignment is `ident = expr`; two tokens of lookahead distinguish
// it from an expression beginning with a variable.
if p.cur().Type == lexer.IDENT && p.peek().Type == lexer.ASSIGN {
name := p.next().Value
p.next() // '='
value, err := p.parseExpression()
if err != nil {
return nil, err
}
return &Assignment{Name: name, Value: value}, nil
}
return p.parseExpression()
}
// parseExpression handles + and -, the loosest-binding operators.
func (p *parser) parseExpression() (Node, error) {
left, err := p.parseTerm()
if err != nil {
return nil, err
}
for p.cur().Type == lexer.PLUS || p.cur().Type == lexer.MINUS {
op := p.next().Type
right, err := p.parseTerm()
if err != nil {
return nil, err
}
left = &BinaryOp{Op: op, Left: left, Right: right}
}
return left, nil
}
// parseTerm handles *, / and %.
func (p *parser) parseTerm() (Node, error) {
left, err := p.parseUnary()
if err != nil {
return nil, err
}
for t := p.cur().Type; t == lexer.STAR || t == lexer.SLASH || t == lexer.PERCENT; t = p.cur().Type {
op := p.next().Type
right, err := p.parseUnary()
if err != nil {
return nil, err
}
left = &BinaryOp{Op: op, Left: left, Right: right}
}
return left, nil
}
func (p *parser) parseUnary() (Node, error) {
if p.cur().Type == lexer.MINUS {
p.next()
operand, err := p.parseUnary()
if err != nil {
return nil, err
}
return &UnaryOp{Op: lexer.MINUS, Operand: operand}, nil
}
if p.cur().Type == lexer.PLUS {
p.next() // unary plus is a no-op
return p.parseUnary()
}
return p.parsePower()
}
// parsePower handles ^, which binds tightest and is right-associative:
// 2^3^2 is 2^(3^2), not (2^3)^2.
func (p *parser) parsePower() (Node, error) {
base, err := p.parsePrimary()
if err != nil {
return nil, err
}
if p.cur().Type == lexer.CARET {
p.next()
// Recursing into parseUnary rather than parsePower is what makes
// the operator right-associative and lets 2^-1 parse.
exponent, err := p.parseUnary()
if err != nil {
return nil, err
}
return &BinaryOp{Op: lexer.CARET, Left: base, Right: exponent}, nil
}
return base, nil
}
func (p *parser) parsePrimary() (Node, error) {
tok := p.cur()
switch tok.Type {
case lexer.NUMBER:
p.next()
v, err := strconv.ParseInt(tok.Value, 10, 64)
if err != nil {
return nil, fmt.Errorf("column %d: %q does not fit in a 64-bit integer", tok.Column, tok.Value)
}
return &Number{Value: v}, nil
case lexer.IDENT:
p.next()
return &Variable{Name: tok.Value}, nil
case lexer.LPAREN:
p.next()
inner, err := p.parseExpression()
if err != nil {
return nil, err
}
if p.cur().Type != lexer.RPAREN {
return nil, fmt.Errorf("column %d: expected ')', got %s", p.cur().Column, p.cur().Type)
}
p.next()
return inner, nil
case lexer.EOF:
return nil, fmt.Errorf("column %d: unexpected end of input", tok.Column)
default:
return nil, fmt.Errorf("column %d: unexpected %s", tok.Column, tok.Type)
}
}