// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com) // All rights reserved. // exprreg.go — Expression parser registries for Pratt parser. // // Three registries: // prefixParsers — unary prefix: -, !, ++, --, <-, ASYNC, AWAIT // postfixParsers — postfix: (), [], :, ., ?:, ++, --, -> // primaryParsers — atoms: INT, STRING, IDENT, (, {, :: // // Adding a new operator = one line in init(). package parser import ( "five/compiler/ast" "five/compiler/token" ) // PrefixParser parses a prefix unary expression. type PrefixParser func(p *Parser) ast.Expr // PostfixParser parses a postfix expression given the left-hand side. type PostfixParser func(p *Parser, x ast.Expr) ast.Expr // PrimaryParser parses an atomic/primary expression. type PrimaryParser func(p *Parser) ast.Expr var ( prefixParsers map[token.Kind]PrefixParser postfixParsers map[token.Kind]PostfixParser primaryParsers map[token.Kind]PrimaryParser ) func init() { prefixParsers = map[token.Kind]PrefixParser{ token.MINUS: prefixUnary(token.MINUS), token.PLUS: prefixPlus, token.NOT: prefixUnary(token.NOT), token.INC: prefixUnary(token.INC), token.DEC: prefixUnary(token.DEC), token.ARROW_LEFT: prefixChanRecv, token.ASYNC_KW: prefixAsync, token.AWAIT_KW: prefixAwait, token.AT: prefixRef, } postfixParsers = map[token.Kind]PostfixParser{ token.LPAREN: postfixCall, token.LBRACKET: postfixIndex, token.COLON: postfixSend, token.QMARK: postfixNilSafe, token.DOT: postfixDot, token.ARROW: postfixAlias, token.INC: postfixIncDec(token.INC), token.DEC: postfixIncDec(token.DEC), token.COLONCOLON: postfixSelfStop, } primaryParsers = map[token.Kind]PrimaryParser{ token.INT: primaryLiteral, token.LONG: primaryLiteral, token.DOUBLE: primaryLiteral, token.STRING: primaryLiteral, token.DATE_LIT: primaryLiteral, token.TRUE: primaryLiteral, token.FALSE: primaryLiteral, token.NIL_LIT: primaryLiteral, token.COLONCOLON: primarySelf, token.LPAREN: primaryParen, token.IF: primaryIf, token.IDENT: primaryIdent, token.AMPERSAND: primaryMacro, token.COLON: primaryWithSend, token.LBRACE: primaryArrayOrBlock, } } // --- Prefix parsers --- func prefixUnary(op token.Kind) PrefixParser { return func(p *Parser) ast.Expr { tok := p.advance() x := p.parseUnaryExpr() return &ast.UnaryExpr{OpPos: tok.Pos, Op: op, X: x} } } func prefixPlus(p *Parser) ast.Expr { p.advance() // unary plus — no-op return p.parseUnaryExpr() } func prefixChanRecv(p *Parser) ast.Expr { pos := p.advance().Pos ch := p.parsePostfixExpr() return &ast.ChanRecvExpr{ArrowPos: pos, Chan: ch} } func prefixAsync(p *Parser) ast.Expr { pos := p.advance().Pos call := p.parsePostfixExpr() return &ast.AsyncExpr{AsyncPos: pos, Call: call} } func prefixAwait(p *Parser) ast.Expr { pos := p.advance().Pos future := p.parsePostfixExpr() return &ast.AwaitExpr{AwaitPos: pos, Future: future} } func prefixRef(p *Parser) ast.Expr { op := p.advance() x := p.parseUnaryExpr() return &ast.RefExpr{AtPos: op.Pos, X: x} } // --- Postfix parsers --- func postfixCall(p *Parser, x ast.Expr) ast.Expr { lp := p.advance().Pos var args []ast.Expr if !p.at(token.RPAREN) { args = p.parseExprList() } rp := p.expect(token.RPAREN).Pos return &ast.CallExpr{Func: x, LParen: lp, Args: args, RParen: rp} } func postfixIndex(p *Parser, x ast.Expr) ast.Expr { lb := p.advance().Pos // Slice syntax detection if p.isSliceSyntax() { var low, high ast.Expr if !p.at(token.COLON) { low = p.parseSliceIndex() } p.expect(token.COLON) if !p.at(token.RBRACKET) { high = p.parseSliceIndex() } rb := p.expect(token.RBRACKET).Pos return &ast.SliceExpr{X: x, LBracket: lb, Low: low, High: high, RBracket: rb} } // Normal array index index := p.parseExpr() rb := token.Position{} for p.match(token.COMMA) { rb = p.current.Pos x = &ast.IndexExpr{X: x, LBracket: lb, Index: index, RBracket: rb} index = p.parseExpr() lb = rb } rb = p.expect(token.RBRACKET).Pos return &ast.IndexExpr{X: x, LBracket: lb, Index: index, RBracket: rb} } func postfixDot(p *Parser, x ast.Expr) ast.Expr { if p.peekLitAt(1) != "" { dotPos := p.advance().Pos member := p.advance() return &ast.DotExpr{X: x, DotPos: dotPos, Member: member.Literal} } return nil // signal: stop postfix loop } func postfixIncDec(op token.Kind) PostfixParser { return func(p *Parser, x ast.Expr) ast.Expr { opPos := p.advance().Pos return &ast.PostfixExpr{X: x, OpPos: opPos, Op: op} } } func postfixSelfStop(p *Parser, x ast.Expr) ast.Expr { return nil // :: after expression — stop } // postfixNilSafe and postfixSend/postfixAlias are complex — kept in expr.go // They call back into the main parser methods. func postfixNilSafe(p *Parser, x ast.Expr) ast.Expr { if p.peekAt(1) != token.COLON { return nil // bare ? = QOut, not postfix } p.advance() // consume ? qpos := p.advance().Pos // consume : methodName := p.expectMethodName().Literal var args []ast.Expr hasParens := false if p.at(token.LPAREN) { hasParens = true p.advance() if !p.at(token.RPAREN) { args = p.parseExprList() } p.expect(token.RPAREN) } return &ast.NilSafeExpr{X: x, QPos: qpos, Method: methodName, Args: args, HasParens: hasParens} } func postfixAlias(p *Parser, x ast.Expr) ast.Expr { arrowPos := p.advance().Pos field := p.parsePrimaryExpr() return &ast.AliasExpr{Alias: x, ArrowPos: arrowPos, Field: field} } func postfixSend(p *Parser, x ast.Expr) ast.Expr { return p.parsePostfixSend(x) } // --- Primary parsers --- func primaryLiteral(p *Parser) ast.Expr { tok := p.advance() return &ast.LiteralExpr{ValuePos: tok.Pos, Kind: tok.Kind, Value: tok.Literal} } func primaryParen(p *Parser) ast.Expr { p.advance() expr := p.parseExpr() for p.match(token.COMMA) { expr = p.parseExpr() } p.expect(token.RPAREN) return expr } func primaryIf(p *Parser) ast.Expr { if p.peekAt(1) == token.LPAREN { return p.parseIIF() } p.error("expected expression, got IF") tok := p.advance() return &ast.LiteralExpr{ValuePos: tok.Pos, Kind: token.NIL_LIT, Value: "NIL"} } func primaryIdent(p *Parser) ast.Expr { return p.parsePrimaryIdent() } func primaryMacro(p *Parser) ast.Expr { return p.parseMacro() } func primaryWithSend(p *Parser) ast.Expr { return p.parsePrimaryWithSend() } func primaryArrayOrBlock(p *Parser) ast.Expr { return p.parseArrayOrBlock() } func primarySelf(p *Parser) ast.Expr { return p.parsePrimarySelf() }