Six audit-driven blockers landed together because they're tangled:
* MENU TO removed from std.ch — the rule expanded to a call to a
nonexistent __MenuTo() RTL symbol, so any user code with `MENU
TO choice` compiled clean and panicked at runtime. Behavior
pre-this-round was a parser silent no-op, which is at least
consistent. Restore that until @ PROMPT (the companion command)
actually lands.
* COUNT now requires `TO <var>`. The earlier `[TO <v>]` optional
bracket was a Harbour-pattern transcription error: the result
template references `<v>` unconditionally, so a bare `COUNT`
expanded to ungrammatical ` := 0 ; dbEval(...)` and the
PRG parser rejected it. Match Harbour's std.ch which makes TO
mandatory.
* UPDATE FROM ... REPLACE now requires `FROM`/`ON`/`REPLACE` all
three. Same root cause as COUNT: the result template uses
`<key>`, `<f1>`, `<x1>` unconditionally; missing any of them
produced broken syntax. Tightened to fail loudly rather than
silently mis-expand.
* CLOSE <unknown_alias> no longer closes the *current* workarea.
SelectByAlias was a silent no-op when the alias was missing,
leaving WASaveAndSelectAlias to evaluate the inner DbCloseArea()
against the originally-selected WA — a real data-loss footgun.
SelectByAlias now returns bool; WASaveAndSelectAlias switches to
the no-area sentinel (0) on miss so the inner expression's
Current() returns nil and short-circuits.
* SUM <x1>, <xN> TO <v1>, <vN> — multi-pair form supported.
Required two pieces:
1. matchSegment's regular-marker stop-boundary now combines
outerTail literals AND the segment's repeat boundary so
`[, <xN>]` doesn't let `<xN>` swallow past the next ','.
2. **Five parser miscompiled comma-separated expressions in
code blocks.** `{|| e1, e2, e3 }` kept only the last expr
and threw away earlier ones at *AST level*, so all their
side effects vanished. New SeqExpr AST node + emitter
(emit each, pop intermediate results) + folding/walk
updates fix the underlying bug, which also unbreaks any
other block that relied on comma sequencing.
* pp.go's `;` continuation joiner now strips exactly one trailing
`;` per iteration, preserving Harbour's `;;` convention (literal
`;` followed by a continuation marker). Without this the SUM
rule's chained `<v1> :=[ <vN> :=] 0 ; ; dbEval(...)` collapsed
to a missing statement separator.
* parseExprStmt's xBase fallback switch is back in sync with
parseIdentStmt — COPY/SORT/COUNT/SUM/AVERAGE/TOTAL/UPDATE/JOIN/
DISPLAY/LIST removed (std.ch handles all of them now). Leaving
them in the fallback masked typos as silent no-ops.
Gates green:
go test ./... : PASS
FiveSql2 SQL:1999 : 43/43
Harbour compat : 56/56
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
959 lines
29 KiB
Go
959 lines
29 KiB
Go
// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
|
|
// All rights reserved.
|
|
|
|
// AST node definitions for the Five language.
|
|
//
|
|
// Design references:
|
|
// - Harbour: HB_EXPR (hbcompdf.h:349) — expression union with ExprType discriminant
|
|
// - Harbour: HB_HFUNC (hbcompdf.h:497) — function with separated pLocals/pStatics/pFields/pMemvars
|
|
// - tsgo: Node with Kind discriminant + nodeData interface (internal/ast/ast.go)
|
|
//
|
|
// Key Harbour rules applied:
|
|
// - LOCAL/STATIC/FIELD declarations must appear at function top, before executable code
|
|
// - FuncDecl separates Decls (declarations) from Body (executable statements)
|
|
// - (expr)->field for dynamic alias access (HB_ET_ALIASEXPR)
|
|
// - &variable for macro (6 subtypes from Harbour: VAR, SYMBOL, ALIASED, EXPR, LIST, PARE)
|
|
package ast
|
|
|
|
import "five/compiler/token"
|
|
|
|
// --- Interfaces ---
|
|
|
|
// Node is the base interface for all AST nodes.
|
|
type Node interface {
|
|
Pos() token.Position
|
|
End() token.Position
|
|
}
|
|
|
|
// Expr represents an expression node (produces a value).
|
|
type Expr interface {
|
|
Node
|
|
exprNode()
|
|
}
|
|
|
|
// Stmt represents a statement node (performs an action).
|
|
type Stmt interface {
|
|
Node
|
|
stmtNode()
|
|
}
|
|
|
|
// Decl represents a declaration node (LOCAL, STATIC, FIELD, etc.).
|
|
type Decl interface {
|
|
Node
|
|
declNode()
|
|
}
|
|
|
|
// --- Program (top-level) ---
|
|
|
|
// File represents a single .prg source file.
|
|
type File struct {
|
|
Name string // filename
|
|
Imports []*ImportDecl
|
|
Decls []Decl // top-level: FUNCTION, PROCEDURE, CLASS, etc.
|
|
}
|
|
|
|
func (f *File) Pos() token.Position {
|
|
if len(f.Decls) > 0 {
|
|
return f.Decls[0].Pos()
|
|
}
|
|
return token.Position{}
|
|
}
|
|
func (f *File) End() token.Position {
|
|
if len(f.Decls) > 0 {
|
|
return f.Decls[len(f.Decls)-1].End()
|
|
}
|
|
return token.Position{}
|
|
}
|
|
|
|
// --- Declarations ---
|
|
|
|
// ImportDecl: IMPORT "package/path" or IMPORT _ "package/path"
|
|
type ImportDecl struct {
|
|
ImportPos token.Position
|
|
Alias string // "" = normal, "_" = blank import, "name" = alias
|
|
Path string // package path
|
|
}
|
|
|
|
func (d *ImportDecl) Pos() token.Position { return d.ImportPos }
|
|
func (d *ImportDecl) End() token.Position { return d.ImportPos }
|
|
func (d *ImportDecl) declNode() {}
|
|
|
|
// FuncDecl represents FUNCTION or PROCEDURE.
|
|
// Harbour: HB_HFUNC — pLocals, pStatics, pFields separated from pcode.
|
|
// LOCAL/STATIC/FIELD must appear before executable code.
|
|
type FuncDecl struct {
|
|
FuncPos token.Position
|
|
Name string
|
|
IsProc bool // PROCEDURE (no return value)
|
|
Params []*ParamDecl // declared parameters
|
|
Decls []Decl // LOCAL, STATIC, FIELD — must come first
|
|
Body []Stmt // executable statements — after declarations
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (d *FuncDecl) Pos() token.Position { return d.FuncPos }
|
|
func (d *FuncDecl) End() token.Position { return d.EndPos }
|
|
func (d *FuncDecl) declNode() {}
|
|
|
|
// ParamDecl represents a function parameter.
|
|
type ParamDecl struct {
|
|
NamePos token.Position
|
|
Name string
|
|
ByRef bool // @param or passed by reference
|
|
AsType string // optional type hint: AS NUMERIC, AS STRING, etc.
|
|
}
|
|
|
|
func (d *ParamDecl) Pos() token.Position { return d.NamePos }
|
|
func (d *ParamDecl) End() token.Position { return d.NamePos }
|
|
func (d *ParamDecl) declNode() {}
|
|
|
|
// VarDecl represents LOCAL, STATIC, PRIVATE, PUBLIC, FIELD declarations.
|
|
// Harbour: LOCAL must be at function top (before executable code).
|
|
// PRIVATE/PUBLIC can appear anywhere (runtime memvar).
|
|
type VarDecl struct {
|
|
DeclPos token.Position
|
|
Scope VarScope
|
|
Vars []*VarInit // one or more: LOCAL a := 1, b := 2, c
|
|
}
|
|
|
|
func (d *VarDecl) Pos() token.Position { return d.DeclPos }
|
|
func (d *VarDecl) End() token.Position {
|
|
if len(d.Vars) > 0 {
|
|
return d.Vars[len(d.Vars)-1].NamePos
|
|
}
|
|
return d.DeclPos
|
|
}
|
|
func (d *VarDecl) declNode() {}
|
|
func (d *VarDecl) stmtNode() {} // PRIVATE/PUBLIC can appear as statements
|
|
|
|
// VarScope indicates where a variable lives.
|
|
type VarScope int
|
|
|
|
const (
|
|
ScopeLocal VarScope = iota // LOCAL — stack, function-top only
|
|
ScopeStatic // STATIC — module-level, function-top only
|
|
ScopePrivate // PRIVATE — runtime memvar, anywhere
|
|
ScopePublic // PUBLIC — runtime memvar, anywhere
|
|
ScopeField // FIELD — database field declaration, function-top only
|
|
)
|
|
|
|
// VarInit represents a single variable with optional initializer.
|
|
type VarInit struct {
|
|
NamePos token.Position
|
|
Name string
|
|
Init Expr // nil if no initializer
|
|
AsType string // optional type hint
|
|
}
|
|
|
|
// ClassDecl represents CLASS ... ENDCLASS.
|
|
type ClassDecl struct {
|
|
ClassPos token.Position
|
|
Name string
|
|
ParentName string // INHERIT FROM parent
|
|
Members []Decl // DATA, METHOD, ACCESS, ASSIGN declarations
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (d *ClassDecl) Pos() token.Position { return d.ClassPos }
|
|
func (d *ClassDecl) End() token.Position { return d.EndPos }
|
|
func (d *ClassDecl) declNode() {}
|
|
|
|
// DataDecl represents DATA member in a class.
|
|
type DataDecl struct {
|
|
DataPos token.Position
|
|
Name string
|
|
Init Expr // INIT expression (nil if none)
|
|
AsType string // AS type hint
|
|
}
|
|
|
|
func (d *DataDecl) Pos() token.Position { return d.DataPos }
|
|
func (d *DataDecl) End() token.Position { return d.DataPos }
|
|
func (d *DataDecl) declNode() {}
|
|
|
|
// MethodDecl represents METHOD declaration in a class or standalone.
|
|
type MethodDecl struct {
|
|
MethodPos token.Position
|
|
Name string
|
|
ClassName string // METHOD name CLASS classname (standalone)
|
|
Params []*ParamDecl
|
|
IsInline bool // INLINE method
|
|
InlineBody Expr // inline expression body — `RETURN <expr>` equivalent
|
|
IsOperator bool // OPERATOR overload — OperatorOp carries the slot
|
|
OperatorOp int // operator slot (hbrt.Op* constant); valid only when IsOperator
|
|
IsSetGet bool // METHOD name(x) SETGET — getter if no arg, setter if arg
|
|
IsAccess bool // ACCESS name METHOD getterName
|
|
IsAssign bool // ASSIGN name METHOD setterName
|
|
AccessName string // property name for ACCESS/ASSIGN
|
|
Decls []Decl
|
|
Body []Stmt
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (d *MethodDecl) Pos() token.Position { return d.MethodPos }
|
|
func (d *MethodDecl) End() token.Position { return d.EndPos }
|
|
func (d *MethodDecl) declNode() {}
|
|
|
|
// GoDumpDecl represents inline Go code from #pragma BEGINDUMP ... #pragma ENDDUMP.
|
|
// Five extension: allows embedding raw Go code directly in PRG files.
|
|
type GoDumpDecl struct {
|
|
DumpPos token.Position
|
|
Code string // raw Go source code
|
|
}
|
|
|
|
func (d *GoDumpDecl) Pos() token.Position { return d.DumpPos }
|
|
func (d *GoDumpDecl) End() token.Position { return d.DumpPos }
|
|
func (d *GoDumpDecl) declNode() {}
|
|
|
|
// --- Expressions ---
|
|
|
|
// LiteralExpr represents a literal value.
|
|
// Harbour: HB_ET_NIL, HB_ET_NUMERIC, HB_ET_STRING, HB_ET_LOGICAL, HB_ET_DATE, HB_ET_TIMESTAMP
|
|
type LiteralExpr struct {
|
|
ValuePos token.Position
|
|
Kind token.Kind // INT, LONG, DOUBLE, STRING, TRUE, FALSE, NIL_LIT, DATE_LIT
|
|
Value string // raw literal text
|
|
}
|
|
|
|
func (e *LiteralExpr) Pos() token.Position { return e.ValuePos }
|
|
func (e *LiteralExpr) End() token.Position { return e.ValuePos }
|
|
func (e *LiteralExpr) exprNode() {}
|
|
|
|
// IdentExpr represents a variable or function name.
|
|
// Harbour: HB_ET_VARIABLE, HB_ET_FUNNAME
|
|
type IdentExpr struct {
|
|
NamePos token.Position
|
|
Name string
|
|
}
|
|
|
|
func (e *IdentExpr) Pos() token.Position { return e.NamePos }
|
|
func (e *IdentExpr) End() token.Position { return e.NamePos }
|
|
func (e *IdentExpr) exprNode() {}
|
|
|
|
// SelfExpr represents :: (Self access in class method).
|
|
// Harbour: HB_ET_SELF
|
|
type SelfExpr struct {
|
|
ColonPos token.Position
|
|
}
|
|
|
|
func (e *SelfExpr) Pos() token.Position { return e.ColonPos }
|
|
func (e *SelfExpr) End() token.Position { return e.ColonPos }
|
|
func (e *SelfExpr) exprNode() {}
|
|
|
|
// BinaryExpr represents a binary operation.
|
|
// Harbour: HB_EO_PLUS, HB_EO_MINUS, HB_EO_EQUAL, etc.
|
|
type BinaryExpr struct {
|
|
Left Expr
|
|
OpPos token.Position
|
|
Op token.Kind
|
|
Right Expr
|
|
}
|
|
|
|
func (e *BinaryExpr) Pos() token.Position { return e.Left.Pos() }
|
|
func (e *BinaryExpr) End() token.Position { return e.Right.End() }
|
|
func (e *BinaryExpr) exprNode() {}
|
|
|
|
// UnaryExpr represents a prefix unary operation.
|
|
// Harbour: HB_EO_NEGATE, HB_EO_NOT, HB_EO_PREINC, HB_EO_PREDEC
|
|
type UnaryExpr struct {
|
|
OpPos token.Position
|
|
Op token.Kind // MINUS, NOT, INC, DEC
|
|
X Expr
|
|
}
|
|
|
|
func (e *UnaryExpr) Pos() token.Position { return e.OpPos }
|
|
func (e *UnaryExpr) End() token.Position { return e.X.End() }
|
|
func (e *UnaryExpr) exprNode() {}
|
|
|
|
// PostfixExpr represents postfix ++ or --.
|
|
// Harbour: HB_EO_POSTINC, HB_EO_POSTDEC
|
|
type PostfixExpr struct {
|
|
X Expr
|
|
OpPos token.Position
|
|
Op token.Kind // INC, DEC
|
|
}
|
|
|
|
func (e *PostfixExpr) Pos() token.Position { return e.X.Pos() }
|
|
func (e *PostfixExpr) End() token.Position { return e.OpPos }
|
|
func (e *PostfixExpr) exprNode() {}
|
|
|
|
// AssignExpr represents assignment: x := value, x += value, etc.
|
|
// Harbour: HB_EO_ASSIGN, HB_EO_PLUSEQ, etc.
|
|
type AssignExpr struct {
|
|
Left Expr
|
|
OpPos token.Position
|
|
Op token.Kind // ASSIGN, PLUSEQ, MINUSEQ, etc.
|
|
Right Expr
|
|
}
|
|
|
|
func (e *AssignExpr) Pos() token.Position { return e.Left.Pos() }
|
|
func (e *AssignExpr) End() token.Position { return e.Right.End() }
|
|
func (e *AssignExpr) exprNode() {}
|
|
|
|
// CallExpr represents a function call: func(args...)
|
|
// Harbour: HB_ET_FUNCALL — pFunName + pParms
|
|
type CallExpr struct {
|
|
Func Expr // function expression (IdentExpr, or macro)
|
|
LParen token.Position
|
|
Args []Expr
|
|
RParen token.Position
|
|
}
|
|
|
|
func (e *CallExpr) Pos() token.Position { return e.Func.Pos() }
|
|
func (e *CallExpr) End() token.Position { return e.RParen }
|
|
func (e *CallExpr) exprNode() {}
|
|
|
|
// DotExpr represents package member access: pkg.Member
|
|
// Used for Go package function calls: sql.Open(), fmt.Println()
|
|
type DotExpr struct {
|
|
X Expr // package (IdentExpr)
|
|
DotPos token.Position
|
|
Member string // function/field name
|
|
}
|
|
|
|
func (e *DotExpr) Pos() token.Position { return e.X.Pos() }
|
|
func (e *DotExpr) End() token.Position { return e.DotPos }
|
|
func (e *DotExpr) exprNode() {}
|
|
|
|
// SendExpr represents method call: obj:method(args...)
|
|
// Harbour: HB_ET_SEND — pObject + szMessage/pMessage + pParms
|
|
type SendExpr struct {
|
|
Object Expr
|
|
ColonPos token.Position
|
|
Method string // static message name
|
|
MacroMethod Expr // if ¯o message (nil for static)
|
|
HasParens bool // true if () present (method call vs field access)
|
|
LParen token.Position
|
|
Args []Expr
|
|
RParen token.Position
|
|
IsAssign bool // obj:prop := value (setter)
|
|
}
|
|
|
|
func (e *SendExpr) Pos() token.Position { return e.Object.Pos() }
|
|
func (e *SendExpr) End() token.Position { return e.RParen }
|
|
func (e *SendExpr) exprNode() {}
|
|
|
|
// IndexExpr represents array index: arr[index]
|
|
// Harbour: HB_ET_ARRAYAT
|
|
type IndexExpr struct {
|
|
X Expr
|
|
LBracket token.Position
|
|
Index Expr
|
|
RBracket token.Position
|
|
}
|
|
|
|
func (e *IndexExpr) Pos() token.Position { return e.X.Pos() }
|
|
func (e *IndexExpr) End() token.Position { return e.RBracket }
|
|
func (e *IndexExpr) exprNode() {}
|
|
|
|
// AliasExpr represents field access: alias->field or (expr)->field
|
|
// Harbour: HB_ET_ALIASVAR, HB_ET_ALIASEXPR
|
|
type AliasExpr struct {
|
|
Alias Expr // IdentExpr for static alias, any Expr for (dynamic)->field
|
|
ArrowPos token.Position
|
|
Field Expr // IdentExpr or MacroExpr
|
|
}
|
|
|
|
func (e *AliasExpr) Pos() token.Position { return e.Alias.Pos() }
|
|
func (e *AliasExpr) End() token.Position { return e.Field.End() }
|
|
func (e *AliasExpr) exprNode() {}
|
|
|
|
// MacroExpr represents macro expansion: &variable or &(expression)
|
|
// Harbour: HB_ET_MACRO with 6 subtypes
|
|
type MacroExpr struct {
|
|
AmpPos token.Position
|
|
Expr Expr // variable or parenthesized expression
|
|
}
|
|
|
|
func (e *MacroExpr) Pos() token.Position { return e.AmpPos }
|
|
func (e *MacroExpr) End() token.Position { return e.Expr.End() }
|
|
func (e *MacroExpr) exprNode() {}
|
|
|
|
// BlockExpr represents a code block: {|params| body}
|
|
// Harbour: HB_ET_CODEBLOCK — pLocals + pExprList
|
|
type BlockExpr struct {
|
|
LBrace token.Position
|
|
Params []string // parameter names (between | |)
|
|
Body Expr // single expression (or comma-separated list)
|
|
RBrace token.Position
|
|
}
|
|
|
|
func (e *BlockExpr) Pos() token.Position { return e.LBrace }
|
|
func (e *BlockExpr) End() token.Position { return e.RBrace }
|
|
func (e *BlockExpr) exprNode() {}
|
|
|
|
// ArrayLitExpr represents a literal array: {1, 2, 3}
|
|
// Harbour: HB_ET_ARRAY
|
|
type ArrayLitExpr struct {
|
|
LBrace token.Position
|
|
Items []Expr
|
|
RBrace token.Position
|
|
}
|
|
|
|
func (e *ArrayLitExpr) Pos() token.Position { return e.LBrace }
|
|
func (e *ArrayLitExpr) End() token.Position { return e.RBrace }
|
|
func (e *ArrayLitExpr) exprNode() {}
|
|
|
|
// SeqExpr is a comma-separated expression list used inside code
|
|
// blocks: `{|p| e1, e2, e3 }`. All sub-expressions are evaluated in
|
|
// order, the last value is the block's return. Without this node the
|
|
// parser kept only the last expr and silently dropped the side
|
|
// effects of every preceding one — a real miscompile that bit
|
|
// `SUM x, y, z TO sx, sy, sz` (only sz accumulated).
|
|
type SeqExpr struct {
|
|
Items []Expr
|
|
StartAt token.Position
|
|
EndAt token.Position
|
|
}
|
|
|
|
func (e *SeqExpr) Pos() token.Position { return e.StartAt }
|
|
func (e *SeqExpr) End() token.Position { return e.EndAt }
|
|
func (e *SeqExpr) exprNode() {}
|
|
|
|
// HashLitExpr represents a literal hash: {"a" => 1, "b" => 2}
|
|
// Harbour: HB_ET_HASH
|
|
type HashLitExpr struct {
|
|
LBrace token.Position
|
|
Keys []Expr
|
|
Values []Expr
|
|
RBrace token.Position
|
|
}
|
|
|
|
func (e *HashLitExpr) Pos() token.Position { return e.LBrace }
|
|
func (e *HashLitExpr) End() token.Position { return e.RBrace }
|
|
func (e *HashLitExpr) exprNode() {}
|
|
|
|
// IIfExpr represents inline if: IIF(cond, trueVal, falseVal)
|
|
// Harbour: HB_ET_IIF
|
|
type IIfExpr struct {
|
|
IfPos token.Position
|
|
Cond Expr
|
|
True Expr
|
|
False Expr
|
|
}
|
|
|
|
func (e *IIfExpr) Pos() token.Position { return e.IfPos }
|
|
func (e *IIfExpr) End() token.Position { return e.False.End() }
|
|
func (e *IIfExpr) exprNode() {}
|
|
|
|
// RefExpr represents pass-by-reference: @variable
|
|
// Harbour: HB_ET_REFERENCE, HB_ET_VARREF, HB_ET_FUNREF
|
|
type RefExpr struct {
|
|
AtPos token.Position
|
|
X Expr
|
|
}
|
|
|
|
func (e *RefExpr) Pos() token.Position { return e.AtPos }
|
|
func (e *RefExpr) End() token.Position { return e.X.End() }
|
|
func (e *RefExpr) exprNode() {}
|
|
|
|
// --- Statements ---
|
|
|
|
// ExprStmt wraps an expression as a statement (function calls, assignments).
|
|
type ExprStmt struct {
|
|
X Expr
|
|
}
|
|
|
|
func (s *ExprStmt) Pos() token.Position { return s.X.Pos() }
|
|
func (s *ExprStmt) End() token.Position { return s.X.End() }
|
|
func (s *ExprStmt) stmtNode() {}
|
|
|
|
// ReturnStmt represents RETURN [expr].
|
|
type ReturnStmt struct {
|
|
ReturnPos token.Position
|
|
Value Expr // first/only return value (nil for bare RETURN)
|
|
Values []Expr // multi-return: RETURN a, b, c (nil if single)
|
|
}
|
|
|
|
func (s *ReturnStmt) Pos() token.Position { return s.ReturnPos }
|
|
func (s *ReturnStmt) End() token.Position {
|
|
if s.Value != nil {
|
|
return s.Value.End()
|
|
}
|
|
return s.ReturnPos
|
|
}
|
|
func (s *ReturnStmt) stmtNode() {}
|
|
|
|
// QOutStmt represents ? expr, expr, ... (shorthand for QOut).
|
|
type QOutStmt struct {
|
|
QPos token.Position
|
|
IsQQ bool // true for ?? (QQOut)
|
|
Exprs []Expr
|
|
}
|
|
|
|
func (s *QOutStmt) Pos() token.Position { return s.QPos }
|
|
func (s *QOutStmt) End() token.Position {
|
|
if len(s.Exprs) > 0 {
|
|
return s.Exprs[len(s.Exprs)-1].End()
|
|
}
|
|
return s.QPos
|
|
}
|
|
func (s *QOutStmt) stmtNode() {}
|
|
|
|
// IfStmt represents IF / ELSEIF / ELSE / ENDIF.
|
|
// Harbour: uses PHB_ELSEIF chain for fixups.
|
|
type IfStmt struct {
|
|
IfPos token.Position
|
|
Cond Expr
|
|
Body []Stmt
|
|
ElseIfs []*ElseIfClause
|
|
ElseBody []Stmt // nil if no ELSE
|
|
EndPos token.Position
|
|
}
|
|
|
|
type ElseIfClause struct {
|
|
ElseIfPos token.Position
|
|
Cond Expr
|
|
Body []Stmt
|
|
}
|
|
|
|
func (s *IfStmt) Pos() token.Position { return s.IfPos }
|
|
func (s *IfStmt) End() token.Position { return s.EndPos }
|
|
func (s *IfStmt) stmtNode() {}
|
|
|
|
// DoWhileStmt represents DO WHILE cond ... ENDDO.
|
|
type DoWhileStmt struct {
|
|
DoPos token.Position
|
|
Cond Expr
|
|
Body []Stmt
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (s *DoWhileStmt) Pos() token.Position { return s.DoPos }
|
|
func (s *DoWhileStmt) End() token.Position { return s.EndPos }
|
|
func (s *DoWhileStmt) stmtNode() {}
|
|
|
|
// ForStmt represents FOR var := start TO end [STEP step] ... NEXT.
|
|
type ForStmt struct {
|
|
ForPos token.Position
|
|
Var string
|
|
Start Expr
|
|
To Expr
|
|
Step Expr // nil for default step 1
|
|
Body []Stmt
|
|
NextPos token.Position
|
|
}
|
|
|
|
func (s *ForStmt) Pos() token.Position { return s.ForPos }
|
|
func (s *ForStmt) End() token.Position { return s.NextPos }
|
|
func (s *ForStmt) stmtNode() {}
|
|
|
|
// ForEachStmt represents FOR EACH var IN collection ... NEXT.
|
|
// Harbour: HB_ENUMERATOR structure.
|
|
type ForEachStmt struct {
|
|
ForPos token.Position
|
|
Var string
|
|
Collection Expr
|
|
Descend bool // FOR EACH DESCEND
|
|
Body []Stmt
|
|
NextPos token.Position
|
|
}
|
|
|
|
func (s *ForEachStmt) Pos() token.Position { return s.ForPos }
|
|
func (s *ForEachStmt) End() token.Position { return s.NextPos }
|
|
func (s *ForEachStmt) stmtNode() {}
|
|
|
|
// SwitchStmt represents SWITCH expr ... CASE ... OTHERWISE ... END.
|
|
// Harbour: HB_SWITCHCMD structure.
|
|
type SwitchStmt struct {
|
|
SwitchPos token.Position
|
|
Expr Expr
|
|
Cases []*CaseClause
|
|
Otherwise []Stmt // nil if no OTHERWISE
|
|
EndPos token.Position
|
|
}
|
|
|
|
type CaseClause struct {
|
|
CasePos token.Position
|
|
Value Expr // case value
|
|
Body []Stmt
|
|
}
|
|
|
|
func (s *SwitchStmt) Pos() token.Position { return s.SwitchPos }
|
|
func (s *SwitchStmt) End() token.Position { return s.EndPos }
|
|
func (s *SwitchStmt) stmtNode() {}
|
|
|
|
// SeqStmt represents BEGIN SEQUENCE ... RECOVER [USING var] ... END.
|
|
type SeqStmt struct {
|
|
BeginPos token.Position
|
|
Body []Stmt
|
|
RecoverVar string // variable name after USING (empty if none)
|
|
RecoverBody []Stmt // nil if no RECOVER
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (s *SeqStmt) Pos() token.Position { return s.BeginPos }
|
|
func (s *SeqStmt) End() token.Position { return s.EndPos }
|
|
func (s *SeqStmt) stmtNode() {}
|
|
|
|
// === Five Go Extensions ===
|
|
|
|
// MultiAssignStmt: a, b, c := expr or a, b := Func()
|
|
// Also handles: a, b := b, a (parallel swap)
|
|
// Blank identifier _ discards the value.
|
|
type MultiAssignStmt struct {
|
|
AssignPos token.Position
|
|
Targets []string // variable names ("_" = discard)
|
|
Values []Expr // right-hand side expressions
|
|
}
|
|
|
|
func (s *MultiAssignStmt) Pos() token.Position { return s.AssignPos }
|
|
func (s *MultiAssignStmt) End() token.Position { return s.AssignPos }
|
|
func (s *MultiAssignStmt) stmtNode() {}
|
|
|
|
// DeferStmt: DEFER expr (execute when function returns)
|
|
type DeferStmt struct {
|
|
DeferPos token.Position
|
|
Call Expr // expression to defer (usually a method/function call)
|
|
}
|
|
|
|
func (s *DeferStmt) Pos() token.Position { return s.DeferPos }
|
|
func (s *DeferStmt) End() token.Position { return s.DeferPos }
|
|
func (s *DeferStmt) stmtNode() {}
|
|
|
|
// ConstDecl: CONST block with optional auto-increment
|
|
type ConstDecl struct {
|
|
ConstPos token.Position
|
|
Items []ConstItem
|
|
}
|
|
|
|
type ConstItem struct {
|
|
Name string
|
|
Value Expr // nil = auto-increment from previous
|
|
}
|
|
|
|
func (d *ConstDecl) Pos() token.Position { return d.ConstPos }
|
|
func (d *ConstDecl) End() token.Position { return d.ConstPos }
|
|
func (d *ConstDecl) declNode() {}
|
|
|
|
// SliceExpr: a[low:high] — sub-array or sub-string
|
|
type SliceExpr struct {
|
|
X Expr
|
|
LBracket token.Position
|
|
Low Expr // nil = from start
|
|
High Expr // nil = to end
|
|
RBracket token.Position
|
|
}
|
|
|
|
func (e *SliceExpr) Pos() token.Position { return e.X.Pos() }
|
|
func (e *SliceExpr) End() token.Position { return e.RBracket }
|
|
func (e *SliceExpr) exprNode() {}
|
|
|
|
// NilSafeExpr: obj?:Method() — returns NIL if obj is NIL
|
|
type NilSafeExpr struct {
|
|
X Expr
|
|
QPos token.Position
|
|
Method string
|
|
Args []Expr
|
|
HasParens bool
|
|
}
|
|
|
|
func (e *NilSafeExpr) Pos() token.Position { return e.X.Pos() }
|
|
func (e *NilSafeExpr) End() token.Position { return e.QPos }
|
|
func (e *NilSafeExpr) exprNode() {}
|
|
|
|
// InterpolatedString: f"Hello {name}, age {age}"
|
|
type InterpolatedString struct {
|
|
FPos token.Position
|
|
Parts []Expr // alternating: LiteralExpr (text), other Expr (interpolated)
|
|
}
|
|
|
|
func (e *InterpolatedString) Pos() token.Position { return e.FPos }
|
|
func (e *InterpolatedString) End() token.Position { return e.FPos }
|
|
func (e *InterpolatedString) exprNode() {}
|
|
|
|
// === Five Concurrency Extensions ===
|
|
|
|
// ChanSendStmt: ch <- value
|
|
type ChanSendStmt struct {
|
|
ChanPos token.Position
|
|
Chan Expr // channel expression
|
|
Value Expr // value to send
|
|
}
|
|
|
|
func (s *ChanSendStmt) Pos() token.Position { return s.ChanPos }
|
|
func (s *ChanSendStmt) End() token.Position { return s.ChanPos }
|
|
func (s *ChanSendStmt) stmtNode() {}
|
|
|
|
// ChanRecvExpr: <- ch (receive from channel, used as expression)
|
|
type ChanRecvExpr struct {
|
|
ArrowPos token.Position
|
|
Chan Expr
|
|
}
|
|
|
|
func (e *ChanRecvExpr) Pos() token.Position { return e.ArrowPos }
|
|
func (e *ChanRecvExpr) End() token.Position { return e.ArrowPos }
|
|
func (e *ChanRecvExpr) exprNode() {}
|
|
|
|
// WatchStmt: WATCH / CASE <- ch / CASE ch <- val / OTHERWISE / ENDWATCH
|
|
type WatchStmt struct {
|
|
WatchPos token.Position
|
|
Cases []*WatchCase
|
|
Otherwise []Stmt
|
|
EndPos token.Position
|
|
}
|
|
|
|
type WatchCase struct {
|
|
CasePos token.Position
|
|
RecvChan Expr // CASE val := <- ch (receive)
|
|
RecvVar string // variable name for received value ("" if none)
|
|
SendChan Expr // CASE ch <- val (send)
|
|
SendVal Expr // value to send
|
|
Body []Stmt
|
|
}
|
|
|
|
func (s *WatchStmt) Pos() token.Position { return s.WatchPos }
|
|
func (s *WatchStmt) End() token.Position { return s.EndPos }
|
|
func (s *WatchStmt) stmtNode() {}
|
|
|
|
// GoBlockStmt: GO { ... } — inline goroutine
|
|
type GoBlockStmt struct {
|
|
GoPos token.Position
|
|
Block *BlockExpr // code block to execute
|
|
}
|
|
|
|
func (s *GoBlockStmt) Pos() token.Position { return s.GoPos }
|
|
func (s *GoBlockStmt) End() token.Position { return s.GoPos }
|
|
func (s *GoBlockStmt) stmtNode() {}
|
|
|
|
// ParallelForStmt: PARALLEL FOR i := 1 TO n / body / NEXT
|
|
type ParallelForStmt struct {
|
|
ForPos token.Position
|
|
Var string
|
|
Start Expr
|
|
To Expr
|
|
Step Expr // nil = default 1
|
|
Body []Stmt
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (s *ParallelForStmt) Pos() token.Position { return s.ForPos }
|
|
func (s *ParallelForStmt) End() token.Position { return s.EndPos }
|
|
func (s *ParallelForStmt) stmtNode() {}
|
|
|
|
// AsyncExpr: ASYNC expr — returns a future/channel
|
|
type AsyncExpr struct {
|
|
AsyncPos token.Position
|
|
Call Expr
|
|
}
|
|
|
|
func (e *AsyncExpr) Pos() token.Position { return e.AsyncPos }
|
|
func (e *AsyncExpr) End() token.Position { return e.AsyncPos }
|
|
func (e *AsyncExpr) exprNode() {}
|
|
|
|
// AwaitExpr: AWAIT future — blocks until result ready
|
|
type AwaitExpr struct {
|
|
AwaitPos token.Position
|
|
Future Expr
|
|
}
|
|
|
|
func (e *AwaitExpr) Pos() token.Position { return e.AwaitPos }
|
|
func (e *AwaitExpr) End() token.Position { return e.AwaitPos }
|
|
func (e *AwaitExpr) exprNode() {}
|
|
|
|
// TimeoutStmt: WITH TIMEOUT n / body / ENDWITH
|
|
type TimeoutStmt struct {
|
|
WithPos token.Position
|
|
Duration Expr // timeout in seconds
|
|
Body []Stmt
|
|
EndPos token.Position
|
|
}
|
|
|
|
func (s *TimeoutStmt) Pos() token.Position { return s.WithPos }
|
|
func (s *TimeoutStmt) End() token.Position { return s.EndPos }
|
|
func (s *TimeoutStmt) stmtNode() {}
|
|
|
|
// === End Five Go Extensions ===
|
|
|
|
// ExitStmt represents EXIT (break out of loop).
|
|
type ExitStmt struct {
|
|
ExitPos token.Position
|
|
}
|
|
|
|
func (s *ExitStmt) Pos() token.Position { return s.ExitPos }
|
|
func (s *ExitStmt) End() token.Position { return s.ExitPos }
|
|
func (s *ExitStmt) stmtNode() {}
|
|
|
|
// LoopStmt represents LOOP (continue to next iteration).
|
|
type LoopStmt struct {
|
|
LoopPos token.Position
|
|
}
|
|
|
|
func (s *LoopStmt) Pos() token.Position { return s.LoopPos }
|
|
func (s *LoopStmt) End() token.Position { return s.LoopPos }
|
|
func (s *LoopStmt) stmtNode() {}
|
|
|
|
// --- xBase command statements ---
|
|
|
|
// UseCmd represents USE [file] [VIA driver] [ALIAS name] [EXCLUSIVE|SHARED]
|
|
type UseCmd struct {
|
|
UsePos token.Position
|
|
File Expr // filename expression (nil = close current)
|
|
Via string // RDD driver name
|
|
Alias string // alias name (static)
|
|
AliasExpr Expr // alias expression for ALIAS (expr) — dynamic alias
|
|
Shared bool // SHARED flag
|
|
ReadOnly bool // READONLY flag
|
|
}
|
|
|
|
func (s *UseCmd) Pos() token.Position { return s.UsePos }
|
|
func (s *UseCmd) End() token.Position { return s.UsePos }
|
|
func (s *UseCmd) stmtNode() {}
|
|
|
|
// SelectCmd represents SELECT area
|
|
type SelectCmd struct {
|
|
SelectPos token.Position
|
|
Area Expr // area number or alias name
|
|
}
|
|
|
|
func (s *SelectCmd) Pos() token.Position { return s.SelectPos }
|
|
func (s *SelectCmd) End() token.Position { return s.SelectPos }
|
|
func (s *SelectCmd) stmtNode() {}
|
|
|
|
// GoCmd represents GO TOP / GO BOTTOM / GO recno / GOTO recno
|
|
type GoCmd struct {
|
|
GoPos token.Position
|
|
Direction string // "TOP", "BOTTOM", or ""
|
|
RecNo Expr // record number expression (nil for TOP/BOTTOM)
|
|
}
|
|
|
|
func (s *GoCmd) Pos() token.Position { return s.GoPos }
|
|
func (s *GoCmd) End() token.Position { return s.GoPos }
|
|
func (s *GoCmd) stmtNode() {}
|
|
|
|
// SkipCmd represents SKIP [n]
|
|
type SkipCmd struct {
|
|
SkipPos token.Position
|
|
Count Expr // nil for SKIP 1
|
|
}
|
|
|
|
func (s *SkipCmd) Pos() token.Position { return s.SkipPos }
|
|
func (s *SkipCmd) End() token.Position { return s.SkipPos }
|
|
func (s *SkipCmd) stmtNode() {}
|
|
|
|
// SeekCmd represents SEEK expr [SOFTSEEK]
|
|
type SeekCmd struct {
|
|
SeekPos token.Position
|
|
Key Expr
|
|
SoftSeek bool
|
|
}
|
|
|
|
func (s *SeekCmd) Pos() token.Position { return s.SeekPos }
|
|
func (s *SeekCmd) End() token.Position { return s.SeekPos }
|
|
func (s *SeekCmd) stmtNode() {}
|
|
|
|
// ReplaceCmd represents REPLACE field WITH expr [, field WITH expr ...]
|
|
type ReplaceCmd struct {
|
|
ReplacePos token.Position
|
|
Fields []ReplaceField
|
|
}
|
|
|
|
type ReplaceField struct {
|
|
Field Expr // field expression (may include alias)
|
|
Value Expr
|
|
}
|
|
|
|
func (s *ReplaceCmd) Pos() token.Position { return s.ReplacePos }
|
|
func (s *ReplaceCmd) End() token.Position { return s.ReplacePos }
|
|
func (s *ReplaceCmd) stmtNode() {}
|
|
|
|
// AppendCmd represents APPEND BLANK
|
|
type AppendCmd struct {
|
|
AppendPos token.Position
|
|
}
|
|
|
|
func (s *AppendCmd) Pos() token.Position { return s.AppendPos }
|
|
func (s *AppendCmd) End() token.Position { return s.AppendPos }
|
|
func (s *AppendCmd) stmtNode() {}
|
|
|
|
// DeleteCmd represents DELETE (mark current record for deletion)
|
|
type DeleteCmd struct {
|
|
DeletePos token.Position
|
|
}
|
|
|
|
func (s *DeleteCmd) Pos() token.Position { return s.DeletePos }
|
|
func (s *DeleteCmd) End() token.Position { return s.DeletePos }
|
|
func (s *DeleteCmd) stmtNode() {}
|
|
|
|
// IndexCmd represents INDEX ON expr [TAG tagname] TO file [FOR cond] [UNIQUE] [DESCENDING]
|
|
type IndexCmd struct {
|
|
IndexPos token.Position
|
|
KeyExpr Expr
|
|
File Expr
|
|
ForCond Expr // nil if no FOR
|
|
TagName string // TAG name for CDX compound index (empty = NTX)
|
|
Unique bool
|
|
Descending bool
|
|
}
|
|
|
|
func (s *IndexCmd) Pos() token.Position { return s.IndexPos }
|
|
func (s *IndexCmd) End() token.Position { return s.IndexPos }
|
|
func (s *IndexCmd) stmtNode() {}
|
|
|
|
// SetCmd represents SET commands: SET FILTER TO expr, SET RELATION TO expr INTO alias, etc.
|
|
type SetCmd struct {
|
|
SetPos token.Position
|
|
Setting string // "FILTER", "RELATION", "ORDER", "INDEX", etc.
|
|
Expr Expr // the value expression
|
|
Extra string // extra info (INTO alias, etc.)
|
|
}
|
|
|
|
func (s *SetCmd) Pos() token.Position { return s.SetPos }
|
|
func (s *SetCmd) End() token.Position { return s.SetPos }
|
|
func (s *SetCmd) stmtNode() {}
|
|
|
|
// AtSayCmd represents @ row, col SAY expr [PICTURE pic]
|
|
type AtSayCmd struct {
|
|
AtPos token.Position
|
|
Row Expr
|
|
Col Expr
|
|
SayExpr Expr
|
|
Picture Expr // nil if no PICTURE
|
|
}
|
|
|
|
func (s *AtSayCmd) Pos() token.Position { return s.AtPos }
|
|
func (s *AtSayCmd) End() token.Position { return s.AtPos }
|
|
func (s *AtSayCmd) stmtNode() {}
|
|
|
|
// AtGetCmd represents @ row, col GET var [PICTURE pic] [VALID valid] [WHEN when]
|
|
type AtGetCmd struct {
|
|
AtPos token.Position
|
|
Row Expr
|
|
Col Expr
|
|
Var Expr // the variable expression
|
|
VarName string // variable name as string
|
|
Picture Expr // nil if no PICTURE
|
|
Valid Expr // nil if no VALID (code block)
|
|
When Expr // nil if no WHEN (code block)
|
|
}
|
|
|
|
func (s *AtGetCmd) Pos() token.Position { return s.AtPos }
|
|
func (s *AtGetCmd) End() token.Position { return s.AtPos }
|
|
func (s *AtGetCmd) stmtNode() {}
|
|
|
|
// AtSayGetCmd represents @ row, col SAY expr GET var [PICTURE pic] [VALID valid] [WHEN when]
|
|
type AtSayGetCmd struct {
|
|
AtPos token.Position
|
|
Row Expr
|
|
Col Expr
|
|
SayExpr Expr
|
|
Var Expr
|
|
VarName string
|
|
Picture Expr
|
|
Valid Expr
|
|
When Expr
|
|
}
|
|
|
|
func (s *AtSayGetCmd) Pos() token.Position { return s.AtPos }
|
|
func (s *AtSayGetCmd) End() token.Position { return s.AtPos }
|
|
func (s *AtSayGetCmd) stmtNode() {}
|
|
|
|
// ReadCmd represents READ [SAVE]
|
|
type ReadCmd struct {
|
|
ReadPos token.Position
|
|
Save bool
|
|
}
|
|
|
|
func (s *ReadCmd) Pos() token.Position { return s.ReadPos }
|
|
func (s *ReadCmd) End() token.Position { return s.ReadPos }
|
|
func (s *ReadCmd) stmtNode() {}
|