Files modified (6): compiler/parser/parser.go — #27: Add currentUpper() helper Replaces 30 strings.ToUpper(p.current.Literal) calls compiler/parser/stmtreg.go — Remove now-unused strings import compiler/parser/expr.go — #30: Document comma expr Harbour semantics compiler/gengo/gengo.go — #31: Replace 8 TODO comments with WARN Macro expr now emits MacroPush() instead of TODO compiler/token/token.go — #25: Replace itoa with strconv.Itoa #41: Add 50+ missing kindNames entries for complete String() Issues resolved: #25,27,30,31,41 Total fixed: 39/53 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
281 lines
8.1 KiB
Go
281 lines
8.1 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"
|
|
)
|
|
|
|
// 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 ---
|
|
|
|
// rewriteAsIdent treats the current keyword token as an identifier for expression parsing.
|
|
// Instead of mutating the token array (which breaks backtracking), it temporarily
|
|
// overwrites the current token and restores nothing — the token is consumed immediately.
|
|
func (p *Parser) rewriteAsIdent(name string) {
|
|
p.current = token.Token{Kind: token.IDENT, Literal: name, Pos: p.current.Pos}
|
|
}
|
|
|
|
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.rewriteAsIdent("Do")
|
|
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.rewriteAsIdent("While")
|
|
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.rewriteAsIdent("for")
|
|
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.rewriteAsIdent("begin")
|
|
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.rewriteAsIdent("return")
|
|
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.current = token.Token{Kind: token.LOCAL, Literal: "LOCAL", Pos: p.current.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.rewriteAsIdent("Go")
|
|
return p.parseExprStmt()
|
|
}
|
|
return p.parseGo()
|
|
}
|
|
|
|
func (p *Parser) stmtDelete() ast.Stmt {
|
|
pos := p.advance().Pos
|
|
if p.current.Kind == token.IDENT {
|
|
upper := p.currentUpper()
|
|
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}}
|
|
}
|