lox-go/parser.go
2025-06-07 21:16:05 -04:00

201 lines
3.6 KiB
Go

package main
type parseError interface {
Error() string
Token() *token
}
type syntaxError struct {
token *token
message string
}
func newSyntaxError(token *token, message string) *syntaxError {
return &syntaxError{token, message}
}
func (s *syntaxError) Token() *token {
return s.token
}
func (s *syntaxError) Error() string {
return s.message
}
type operandFunc func() (expr, error)
type parser struct {
tokens []*token
current int
}
func newParser(tokens []*token) *parser {
return &parser{
tokens: tokens,
current: 0,
}
}
func (p *parser) Parse() (expr, error) {
e, err := p.expression()
if err != nil {
return e, err
}
return e, err
}
// expression -> equality
func (p *parser) expression() (expr, error) {
return p.equality()
}
func (p *parser) parseLeftAssocBinOps(operand operandFunc, tokenTypes ...tokenType) (expr, error) {
e, err := operand()
if err != nil {
return e, err
}
for p.match(tokenTypes...) {
op := p.previous()
r, err := operand()
if err != nil {
return e, err
}
e = &binaryExpr{e, op, r}
}
return e, nil
}
// eqality -> comparison ( ("!=" | "==") comparison )*
func (p *parser) equality() (expr, error) {
return p.parseLeftAssocBinOps(
p.comparison,
tokenTypeBangEq, tokenTypeEqualEqual,
)
}
// comparison -> term ( ( ">" | ">=" | "<" | "<=" ) term )*
func (p *parser) comparison() (expr, error) {
return p.parseLeftAssocBinOps(
p.term,
tokenTypeGreater, tokenTypeGreaterEq,
tokenTypeLess, tokenTypeLessEq,
)
}
// term -> factor ( ( "-" | "+" ) factor )*
func (p *parser) term() (expr, error) {
return p.parseLeftAssocBinOps(
p.factor,
tokenTypeMinus, tokenTypePlus,
)
}
// factor -> unary ( ( "*" | "/" ) unary )*
func (p *parser) factor() (expr, error) {
return p.parseLeftAssocBinOps(
p.unary,
tokenTypeSlash, tokenTypeStar,
)
}
// unary -> ( "!" | "-" ) unary | primary;
func (p *parser) unary() (expr, error) {
if p.match(tokenTypeBang, tokenTypeMinus) {
op := p.previous()
r, err := p.unary()
return &unaryExpr{op, r}, err
}
return p.primary()
}
// primary -> STRING | NUMBER | "true" | "false" | "nil" | "(" expression ")"
func (p *parser) primary() (expr, error) {
if p.match(tokenTypeTrue) {
return &literalExpr{true}, nil
}
if p.match(tokenTypeFalse) {
return &literalExpr{false}, nil
}
if p.match(tokenTypeNil) {
return &literalExpr{nil}, nil
}
if p.match(tokenTypeString, tokenTypeNumber) {
return &literalExpr{p.previous().Literal}, nil
}
if p.match(tokenTypeLeftParen) {
e, err := p.expression()
if err != nil {
return nil, err
}
_, err = p.consume(tokenTypeRightParen, "expected ')' after expression")
return &groupingExpr{e}, err
}
return nil, newSyntaxError(p.peek(), "expected expression")
}
func (p *parser) consume(tokenType tokenType, msg string) (*token, error) {
if p.check(tokenType) {
return p.advance(), nil
}
return nil, newSyntaxError(p.peek(), msg)
}
func (p *parser) synchronize() {
p.advance()
for !p.isAtEnd() {
if p.previous().Type == tokenTypeSemicolon {
return
}
if isKeyword(p.peek()) {
return
}
p.advance()
}
}
func (p *parser) match(tokenTypes ...tokenType) bool {
for _, t := range tokenTypes {
if p.check(t) {
p.advance()
return true
}
}
return false
}
func (p *parser) check(t tokenType) bool {
if p.isAtEnd() {
return false
}
return p.peek().Type == t
}
func (p *parser) advance() *token {
if !p.isAtEnd() {
p.current += 1
}
return p.previous()
}
func (p *parser) isAtEnd() bool {
return p.peek().Type == tokenTypeEOF
}
func (p *parser) peek() *token {
return p.tokens[p.current]
}
func (p *parser) previous() *token {
return p.tokens[p.current-1]
}