Files
five/compiler/parser/stmtreg.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- 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>
2026-03-31 09:41:50 +09:00

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}}
}