- 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>
288 lines
8.2 KiB
Go
288 lines
8.2 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// stmtreg.go — Statement parser registry.
|
|
//
|
|
// Instead of a 800+ line switch in parseStmt(), each statement type
|
|
// registers its parser function. New statements can be added by
|
|
// simply adding one line to initStmtRegistry().
|
|
//
|
|
// Pattern: token.Kind → func(*Parser) ast.Stmt
|
|
|
|
package parser
|
|
|
|
import (
|
|
"five/compiler/ast"
|
|
"five/compiler/token"
|
|
"strings"
|
|
)
|
|
|
|
// StmtParser is a function that parses a statement starting with the current token.
|
|
type StmtParser func(p *Parser) ast.Stmt
|
|
|
|
// stmtRegistry maps token kinds to their statement parsers.
|
|
var stmtRegistry map[token.Kind]StmtParser
|
|
|
|
func init() {
|
|
stmtRegistry = map[token.Kind]StmtParser{
|
|
// Control flow
|
|
token.IF: (*Parser).stmtIf,
|
|
token.DO: (*Parser).stmtDo,
|
|
token.WHILE: (*Parser).stmtWhile,
|
|
token.FOR: (*Parser).stmtFor,
|
|
token.BEGIN: (*Parser).stmtBegin,
|
|
token.SWITCH: (*Parser).stmtSwitch,
|
|
token.RETURN: (*Parser).stmtReturn,
|
|
token.EXIT: (*Parser).stmtExit,
|
|
token.LOOP: (*Parser).stmtLoop,
|
|
|
|
// I/O
|
|
token.QMARK: (*Parser).stmtQOut,
|
|
token.QQMARK: (*Parser).stmtQQOut,
|
|
|
|
// Variables
|
|
token.PRIVATE: (*Parser).stmtPrivate,
|
|
token.PUBLIC: (*Parser).stmtPublic,
|
|
token.LOCAL: (*Parser).stmtVarDecl,
|
|
token.STATIC: (*Parser).stmtVarDecl,
|
|
token.PARAMETERS: (*Parser).stmtParameters,
|
|
token.DECLARE: (*Parser).stmtDeclare,
|
|
|
|
// xBase database
|
|
token.USE: (*Parser).stmtUse,
|
|
token.SELECT: (*Parser).stmtSelect,
|
|
token.GO: (*Parser).stmtGo,
|
|
token.GOTO: (*Parser).stmtGo,
|
|
token.SKIP_KW: (*Parser).stmtSkip,
|
|
token.SEEK: (*Parser).stmtSeek,
|
|
token.REPLACE: (*Parser).stmtReplace,
|
|
token.APPEND: (*Parser).stmtAppend,
|
|
token.DELETE_KW: (*Parser).stmtDelete,
|
|
token.RECALL: (*Parser).stmtRecallPackZap,
|
|
token.PACK: (*Parser).stmtRecallPackZap,
|
|
token.ZAP: (*Parser).stmtRecallPackZap,
|
|
token.INDEX: (*Parser).stmtIndex,
|
|
token.SET: (*Parser).stmtSet,
|
|
|
|
// Screen
|
|
token.AT: (*Parser).stmtAt,
|
|
|
|
// Five Go extensions
|
|
token.DEFER_KW: (*Parser).stmtDefer,
|
|
token.CONST_KW: (*Parser).stmtConst,
|
|
token.WATCH_KW: (*Parser).stmtWatch,
|
|
token.WITH: (*Parser).stmtWith,
|
|
token.PARALLEL_KW: (*Parser).stmtParallel,
|
|
token.SPAWN_KW: (*Parser).stmtSpawn,
|
|
token.ARROW_LEFT: (*Parser).stmtArrowLeft,
|
|
}
|
|
}
|
|
|
|
// lookupStmtParser finds a registered parser for the current token.
|
|
func (p *Parser) lookupStmtParser() StmtParser {
|
|
if fn, ok := stmtRegistry[p.current.Kind]; ok {
|
|
return fn
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// --- Thin wrappers: each calls the existing parse method ---
|
|
|
|
func (p *Parser) stmtIf() ast.Stmt {
|
|
if p.peekAt(1) == token.LPAREN {
|
|
if p.looksLikeIIF() {
|
|
return p.parseExprStmt()
|
|
}
|
|
}
|
|
return p.parseIf()
|
|
}
|
|
|
|
func (p *Parser) stmtDo() ast.Stmt {
|
|
if p.peekAt(1) == token.LPAREN {
|
|
p.tokens[p.pos].Kind = token.IDENT
|
|
p.tokens[p.pos].Literal = "Do"
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseExprStmt()
|
|
}
|
|
if p.peekAt(1) == token.CASE || token.LookupKeyword(p.peekLitAt(1)) == token.CASE {
|
|
return p.parseDoCase()
|
|
}
|
|
if p.peekAt(1) == token.WHILE {
|
|
return p.parseDoWhile()
|
|
}
|
|
if p.peekAt(1) == token.IDENT {
|
|
return p.parseDoProc()
|
|
}
|
|
return p.parseDoWhile()
|
|
}
|
|
|
|
func (p *Parser) stmtWhile() ast.Stmt {
|
|
if p.peekAt(1) == token.LPAREN {
|
|
p.tokens[p.pos].Kind = token.IDENT
|
|
p.tokens[p.pos].Literal = "While"
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseExprStmt()
|
|
}
|
|
return p.parseDoWhile()
|
|
}
|
|
|
|
func (p *Parser) stmtFor() ast.Stmt {
|
|
next := p.peekAt(1)
|
|
if next == token.ASSIGN || next == token.LPAREN ||
|
|
next == token.PLUSEQ || next == token.MINUSEQ {
|
|
p.tokens[p.pos].Kind = token.IDENT
|
|
p.tokens[p.pos].Literal = "for"
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseExprStmt()
|
|
}
|
|
return p.parseFor()
|
|
}
|
|
|
|
func (p *Parser) stmtBegin() ast.Stmt {
|
|
if p.peekAt(1) != token.SEQUENCE && p.peekAt(1) != token.NEWLINE && p.peekAt(1) != token.EOF {
|
|
p.tokens[p.pos].Kind = token.IDENT
|
|
p.tokens[p.pos].Literal = "begin"
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseExprStmt()
|
|
}
|
|
return p.parseBeginSequence()
|
|
}
|
|
|
|
func (p *Parser) stmtSwitch() ast.Stmt { return p.parseSwitch() }
|
|
|
|
func (p *Parser) stmtReturn() ast.Stmt {
|
|
next := p.peekAt(1)
|
|
if next == token.ASSIGN || next == token.PLUSEQ || next == token.MINUSEQ {
|
|
p.tokens[p.pos].Kind = token.IDENT
|
|
p.tokens[p.pos].Literal = "return"
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseExprStmt()
|
|
}
|
|
return p.parseReturn()
|
|
}
|
|
|
|
func (p *Parser) stmtExit() ast.Stmt {
|
|
pos := p.advance().Pos
|
|
return &ast.ExitStmt{ExitPos: pos}
|
|
}
|
|
|
|
func (p *Parser) stmtLoop() ast.Stmt {
|
|
pos := p.advance().Pos
|
|
return &ast.LoopStmt{LoopPos: pos}
|
|
}
|
|
|
|
func (p *Parser) stmtQOut() ast.Stmt { return p.parseQOut(false) }
|
|
func (p *Parser) stmtQQOut() ast.Stmt { return p.parseQOut(true) }
|
|
|
|
func (p *Parser) stmtPrivate() ast.Stmt { return p.parsePrivatePublic(ast.ScopePrivate) }
|
|
func (p *Parser) stmtPublic() ast.Stmt { return p.parsePrivatePublic(ast.ScopePublic) }
|
|
func (p *Parser) stmtVarDecl() ast.Stmt { return p.parseVarDecl() }
|
|
|
|
func (p *Parser) stmtParameters() ast.Stmt {
|
|
p.tokens[p.pos].Kind = token.LOCAL
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseVarDecl()
|
|
}
|
|
|
|
func (p *Parser) stmtDeclare() ast.Stmt {
|
|
p.skipToEndOfLine()
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
|
}
|
|
|
|
func (p *Parser) stmtUse() ast.Stmt { return p.parseUse() }
|
|
func (p *Parser) stmtSelect() ast.Stmt { return p.parseSelect() }
|
|
func (p *Parser) stmtSkip() ast.Stmt { return p.parseSkip() }
|
|
func (p *Parser) stmtSeek() ast.Stmt { return p.parseSeek() }
|
|
func (p *Parser) stmtReplace() ast.Stmt { return p.parseReplace() }
|
|
func (p *Parser) stmtAppend() ast.Stmt { return p.parseAppend() }
|
|
func (p *Parser) stmtIndex() ast.Stmt { return p.parseIndex() }
|
|
func (p *Parser) stmtAt() ast.Stmt { return p.parseAtCmd() }
|
|
|
|
func (p *Parser) stmtGo() ast.Stmt {
|
|
if p.current.Kind == token.GO && p.peekAt(1) == token.LPAREN {
|
|
p.tokens[p.pos].Kind = token.IDENT
|
|
p.tokens[p.pos].Literal = "Go"
|
|
p.current = p.tokens[p.pos]
|
|
return p.parseExprStmt()
|
|
}
|
|
return p.parseGo()
|
|
}
|
|
|
|
func (p *Parser) stmtDelete() ast.Stmt {
|
|
pos := p.advance().Pos
|
|
if p.current.Kind == token.IDENT {
|
|
upper := strings.ToUpper(p.current.Literal)
|
|
if upper == "FILE" {
|
|
p.skipToEndOfLine()
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
|
}
|
|
if upper == "ALL" || upper == "TAG" {
|
|
p.skipToEndOfLine()
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
|
}
|
|
}
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.CallExpr{
|
|
Func: &ast.IdentExpr{NamePos: pos, Name: "DbDelete"},
|
|
}}
|
|
}
|
|
|
|
func (p *Parser) stmtRecallPackZap() ast.Stmt {
|
|
tok := p.advance()
|
|
var fname string
|
|
switch tok.Kind {
|
|
case token.RECALL:
|
|
fname = "DbRecall"
|
|
case token.PACK:
|
|
fname = "__DbPack"
|
|
case token.ZAP:
|
|
fname = "__DbZap"
|
|
}
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.CallExpr{
|
|
Func: &ast.IdentExpr{NamePos: tok.Pos, Name: fname},
|
|
}}
|
|
}
|
|
|
|
func (p *Parser) stmtSet() ast.Stmt {
|
|
// SET command — skip to EOL (SET COLOR, SET FILTER, SET ORDER, etc.)
|
|
p.skipToEndOfLine()
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
|
}
|
|
|
|
func (p *Parser) stmtDefer() ast.Stmt { return p.parseDefer() }
|
|
func (p *Parser) stmtConst() ast.Stmt { return p.parseConstBlock() }
|
|
func (p *Parser) stmtWatch() ast.Stmt { return p.parseWatch() }
|
|
func (p *Parser) stmtParallel() ast.Stmt { return p.parseParallelFor() }
|
|
|
|
func (p *Parser) stmtWith() ast.Stmt {
|
|
if p.peekAt(1) == token.TIMEOUT_KW {
|
|
return p.parseWithTimeout()
|
|
}
|
|
p.skipToEndOfLine()
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.LiteralExpr{Kind: token.NIL_LIT, Value: "NIL"}}
|
|
}
|
|
|
|
func (p *Parser) stmtSpawn() ast.Stmt {
|
|
goPos := p.advance().Pos
|
|
block := p.parseArrayOrBlock()
|
|
if blk, ok := block.(*ast.BlockExpr); ok {
|
|
p.expectEndOfStmt()
|
|
return &ast.GoBlockStmt{GoPos: goPos, Block: blk}
|
|
}
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: block}
|
|
}
|
|
|
|
func (p *Parser) stmtArrowLeft() ast.Stmt {
|
|
pos := p.advance().Pos
|
|
ch := p.parseExpr()
|
|
p.expectEndOfStmt()
|
|
return &ast.ExprStmt{X: &ast.ChanRecvExpr{ArrowPos: pos, Chan: ch}}
|
|
}
|