// 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(...)` with parentheses is a function call (the Harbour Set() // runtime function for reading/writing SET slots), not a SET command. // Treat it as an expression statement so the args reach the call. if p.peekAt(1) == token.LPAREN { p.rewriteAsIdent("Set") return p.parseExprStmt() } return p.parseSet() } 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}} }