blob: c7be53b1b42097a027a2b381e34cec9498545f7a [file] [log] [blame]
package parser
import (
"fmt"
"matheval/ast"
"matheval/token"
"strconv"
)
// Parse converts a slice of tokens into an AST.
// Returns an error for malformed expressions.
func Parse(tokens []token.Token) (ast.Node, error) {
p := &parser{tokens: tokens}
node, err := p.expr()
if err != nil {
return nil, err
}
// Ensure all tokens have been consumed (only EOF remains).
if p.current().Type != token.EOF {
tok := p.current()
return nil, fmt.Errorf("unexpected token %v at position %d", tok.Type, tok.Pos)
}
return node, nil
}
// parser holds the state for a single parse operation.
type parser struct {
tokens []token.Token
pos int
}
// current returns the token at the current position.
func (p *parser) current() token.Token {
if p.pos >= len(p.tokens) {
return token.Token{Type: token.EOF}
}
return p.tokens[p.pos]
}
// advance moves to the next token and returns the previous one.
func (p *parser) advance() token.Token {
tok := p.current()
p.pos++
return tok
}
// expect consumes a token of the given type or returns an error.
func (p *parser) expect(typ token.Type) (token.Token, error) {
tok := p.current()
if tok.Type != typ {
return tok, fmt.Errorf("expected %v but got %v at position %d", typ, tok.Type, tok.Pos)
}
p.advance()
return tok, nil
}
// expr → term (('+' | '-') term)*
func (p *parser) expr() (ast.Node, error) {
left, err := p.term()
if err != nil {
return nil, err
}
for p.current().Type == token.Plus || p.current().Type == token.Minus {
op := p.advance()
right, err := p.term()
if err != nil {
return nil, err
}
left = &ast.BinaryExpr{
Op: op.Type,
Left: left,
Right: right,
}
}
return left, nil
}
// term → factor (('*' | '/') factor)*
func (p *parser) term() (ast.Node, error) {
left, err := p.factor()
if err != nil {
return nil, err
}
for p.current().Type == token.Star || p.current().Type == token.Slash {
op := p.advance()
right, err := p.factor()
if err != nil {
return nil, err
}
left = &ast.BinaryExpr{
Op: op.Type,
Left: left,
Right: right,
}
}
return left, nil
}
// factor → NUMBER | '(' expr ')'
func (p *parser) factor() (ast.Node, error) {
tok := p.current()
switch tok.Type {
case token.Number:
p.advance()
val, err := strconv.ParseFloat(tok.Literal, 64)
if err != nil {
return nil, fmt.Errorf("invalid number %q at position %d: %w", tok.Literal, tok.Pos, err)
}
return &ast.NumberLit{Value: val}, nil
case token.LParen:
p.advance() // consume '('
node, err := p.expr()
if err != nil {
return nil, err
}
if _, err := p.expect(token.RParen); err != nil {
return nil, fmt.Errorf("missing closing parenthesis at position %d", p.current().Pos)
}
return node, nil
default:
return nil, fmt.Errorf("unexpected token %v at position %d", tok.Type, tok.Pos)
}
}