- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline - Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch - RTL: 351 Harbour-compatible functions - RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization - Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec) - HB_FUNC API: Full Harbour C API compatible Go bridge - Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT - Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST - Macro Compiler: Runtime AST parsing and evaluation - Debugger: TUI debugger with source display, breakpoints, stepping - FRB: Native + Pcode dual mode runtime binary - Tests: 13 packages ALL PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
259 lines
6.5 KiB
Go
259 lines
6.5 KiB
Go
// 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()
|
|
}
|