|
|
|
|
@@ -41,6 +41,9 @@ type Generator struct {
|
|
|
|
|
hoistedDW bool // DO WHILE has hoisted _dwa/_darea
|
|
|
|
|
symCache map[string]string // symbol name → cached variable name (nil = not caching)
|
|
|
|
|
Debug bool // if true, emit t.DebugLine() calls
|
|
|
|
|
forLabelSeq int // monotonic counter for FOR..NEXT LOOP labels
|
|
|
|
|
curForLabel string // current FOR loop's LOOP goto label ("" if not in FOR)
|
|
|
|
|
blockSeq int // monotonic counter for unique closure capture names
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type symbolEntry struct {
|
|
|
|
|
@@ -122,7 +125,28 @@ func doGenerate(file *ast.File, debug, library bool) string {
|
|
|
|
|
g.emitMain()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return g.buf.String()
|
|
|
|
|
// Patch deferred imports: inline RTL may add "fmt"/"strings" after header was emitted.
|
|
|
|
|
result := g.buf.String()
|
|
|
|
|
var deferred string
|
|
|
|
|
if g.imports["fmt"] && !strings.Contains(result, "\"fmt\"") {
|
|
|
|
|
deferred += "\t\"fmt\"\n"
|
|
|
|
|
}
|
|
|
|
|
if g.imports["strings"] && !strings.Contains(result, "\"strings\"") {
|
|
|
|
|
deferred += "\t\"strings\"\n"
|
|
|
|
|
}
|
|
|
|
|
result = strings.Replace(result, "\t/*DEFERRED_IMPORTS*/\n", deferred, 1)
|
|
|
|
|
// Patch guards
|
|
|
|
|
if g.imports["strings"] {
|
|
|
|
|
result = strings.Replace(result, "/*GUARD_STRINGS*/", "var _ = strings.TrimLeft", 1)
|
|
|
|
|
} else {
|
|
|
|
|
result = strings.Replace(result, "/*GUARD_STRINGS*/\n", "", 1)
|
|
|
|
|
}
|
|
|
|
|
if g.imports["fmt"] {
|
|
|
|
|
result = strings.Replace(result, "/*GUARD_FMT*/", "var _ = fmt.Sprintf", 1)
|
|
|
|
|
} else {
|
|
|
|
|
result = strings.Replace(result, "/*GUARD_FMT*/\n", "", 1)
|
|
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Emit infrastructure ---
|
|
|
|
|
@@ -156,7 +180,7 @@ func (g *Generator) emitHeader() {
|
|
|
|
|
g.writeln("package main")
|
|
|
|
|
g.writeln("")
|
|
|
|
|
|
|
|
|
|
// Imports
|
|
|
|
|
// Imports (deferred placeholder for imports discovered during body emission)
|
|
|
|
|
g.writeln("import (")
|
|
|
|
|
g.indent++
|
|
|
|
|
for imp := range g.imports {
|
|
|
|
|
@@ -166,6 +190,7 @@ func (g *Generator) emitHeader() {
|
|
|
|
|
g.writeln(fmt.Sprintf("%q", imp))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
g.writeln("/*DEFERRED_IMPORTS*/")
|
|
|
|
|
g.indent--
|
|
|
|
|
g.writeln(")")
|
|
|
|
|
g.writeln("")
|
|
|
|
|
@@ -176,12 +201,11 @@ func (g *Generator) emitHeader() {
|
|
|
|
|
g.writeln("var _ = hbrdd.NewWorkAreaManager")
|
|
|
|
|
g.writeln("var _ dbf.DBFDriver")
|
|
|
|
|
}
|
|
|
|
|
if g.imports["strings"] {
|
|
|
|
|
g.writeln("var _ = strings.TrimLeft")
|
|
|
|
|
}
|
|
|
|
|
if g.imports["fmt"] {
|
|
|
|
|
g.writeln("var _ = fmt.Sprintf")
|
|
|
|
|
}
|
|
|
|
|
// Always emit — deferred inline RTL may add these imports after header.
|
|
|
|
|
// If not actually imported, the DEFERRED_IMPORTS patch won't add them and
|
|
|
|
|
// Go compiler will error. But we also strip unused guards in the patch step.
|
|
|
|
|
g.writeln("/*GUARD_STRINGS*/")
|
|
|
|
|
g.writeln("/*GUARD_FMT*/")
|
|
|
|
|
g.writeln("")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -355,7 +379,7 @@ func (g *Generator) emitFuncDecl(fn *ast.FuncDecl) {
|
|
|
|
|
for _, v := range vd.Vars {
|
|
|
|
|
if v.Init != nil {
|
|
|
|
|
g.emitExpr(v.Init)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocal(%d)", localIdx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", localIdx))
|
|
|
|
|
}
|
|
|
|
|
localIdx++
|
|
|
|
|
}
|
|
|
|
|
@@ -377,13 +401,13 @@ func (g *Generator) buildLocalMap(fn *ast.FuncDecl) localMap {
|
|
|
|
|
m := make(localMap)
|
|
|
|
|
idx := 1
|
|
|
|
|
for _, p := range fn.Params {
|
|
|
|
|
m[p.Name] = idx
|
|
|
|
|
m[strings.ToUpper(p.Name)] = idx
|
|
|
|
|
idx++
|
|
|
|
|
}
|
|
|
|
|
for _, d := range fn.Decls {
|
|
|
|
|
if vd, ok := d.(*ast.VarDecl); ok && vd.Scope == ast.ScopeLocal {
|
|
|
|
|
for _, v := range vd.Vars {
|
|
|
|
|
m[v.Name] = idx
|
|
|
|
|
m[strings.ToUpper(v.Name)] = idx
|
|
|
|
|
idx++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -391,6 +415,46 @@ func (g *Generator) buildLocalMap(fn *ast.FuncDecl) localMap {
|
|
|
|
|
return m
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// scanBodyLocals recursively scans statements for LOCAL declarations,
|
|
|
|
|
// adding them to the local map with pre-assigned indices.
|
|
|
|
|
// This ensures mid-function LOCALs are known at compile time.
|
|
|
|
|
func scanBodyLocals(stmts []ast.Stmt, m localMap, idx *int) {
|
|
|
|
|
for _, s := range stmts {
|
|
|
|
|
switch st := s.(type) {
|
|
|
|
|
case *ast.VarDecl:
|
|
|
|
|
if st.Scope == ast.ScopeLocal {
|
|
|
|
|
for _, v := range st.Vars {
|
|
|
|
|
name := strings.ToUpper(v.Name)
|
|
|
|
|
if _, exists := m[name]; !exists {
|
|
|
|
|
m[name] = *idx
|
|
|
|
|
(*idx)++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case *ast.IfStmt:
|
|
|
|
|
scanBodyLocals(st.Body, m, idx)
|
|
|
|
|
for _, ei := range st.ElseIfs {
|
|
|
|
|
scanBodyLocals(ei.Body, m, idx)
|
|
|
|
|
}
|
|
|
|
|
scanBodyLocals(st.ElseBody, m, idx)
|
|
|
|
|
case *ast.DoWhileStmt:
|
|
|
|
|
scanBodyLocals(st.Body, m, idx)
|
|
|
|
|
case *ast.ForStmt:
|
|
|
|
|
scanBodyLocals(st.Body, m, idx)
|
|
|
|
|
case *ast.ForEachStmt:
|
|
|
|
|
scanBodyLocals(st.Body, m, idx)
|
|
|
|
|
case *ast.SwitchStmt:
|
|
|
|
|
for _, c := range st.Cases {
|
|
|
|
|
scanBodyLocals(c.Body, m, idx)
|
|
|
|
|
}
|
|
|
|
|
scanBodyLocals(st.Otherwise, m, idx)
|
|
|
|
|
case *ast.SeqStmt:
|
|
|
|
|
scanBodyLocals(st.Body, m, idx)
|
|
|
|
|
scanBodyLocals(st.RecoverBody, m, idx)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- Statement emission ---
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
|
|
|
|
|
@@ -441,7 +505,12 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
|
|
|
|
|
g.writeln("break")
|
|
|
|
|
|
|
|
|
|
case *ast.LoopStmt:
|
|
|
|
|
g.writeln("continue")
|
|
|
|
|
if g.curForLabel != "" {
|
|
|
|
|
// Inside FOR..NEXT: goto label before increment (continue would skip it)
|
|
|
|
|
g.writeln("goto " + g.curForLabel)
|
|
|
|
|
} else {
|
|
|
|
|
g.writeln("continue")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case *ast.MultiAssignStmt:
|
|
|
|
|
g.emitMultiAssign(s, locals)
|
|
|
|
|
@@ -627,13 +696,9 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitMidVarDecl(s *ast.VarDecl, locals localMap) {
|
|
|
|
|
// LOCAL declared in mid-function: allocate new local slots dynamically
|
|
|
|
|
// For now, emit as local variable with initialization
|
|
|
|
|
for _, v := range s.Vars {
|
|
|
|
|
// Find or assign local index
|
|
|
|
|
idx, found := locals[v.Name]
|
|
|
|
|
idx, found := locals[strings.ToUpper(v.Name)]
|
|
|
|
|
if !found {
|
|
|
|
|
// Assign next available slot
|
|
|
|
|
maxIdx := 0
|
|
|
|
|
for _, i := range locals {
|
|
|
|
|
if i > maxIdx {
|
|
|
|
|
@@ -641,11 +706,11 @@ func (g *Generator) emitMidVarDecl(s *ast.VarDecl, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
idx = maxIdx + 1
|
|
|
|
|
locals[v.Name] = idx
|
|
|
|
|
locals[strings.ToUpper(v.Name)] = idx
|
|
|
|
|
}
|
|
|
|
|
if v.Init != nil {
|
|
|
|
|
g.emitExpr(v.Init)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -676,7 +741,7 @@ func (g *Generator) emitExprStmt(s *ast.ExprStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
// Bare identifier as statement (e.g., CLS, CLEAR) — treat as zero-arg function call
|
|
|
|
|
if ident, ok := s.X.(*ast.IdentExpr); ok {
|
|
|
|
|
if _, found := locals[ident.Name]; !found {
|
|
|
|
|
if _, found := locals[strings.ToUpper(ident.Name)]; !found {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushSymbol(t.VM().FindSymbol(%q))", strings.ToUpper(ident.Name)))
|
|
|
|
|
g.writeln("t.PushNil()")
|
|
|
|
|
g.writeln("t.Do(0)")
|
|
|
|
|
@@ -687,7 +752,8 @@ func (g *Generator) emitExprStmt(s *ast.ExprStmt, locals localMap) {
|
|
|
|
|
if pf, ok := s.X.(*ast.PostfixExpr); ok {
|
|
|
|
|
// Local variable: n++
|
|
|
|
|
if ident, ok := pf.X.(*ast.IdentExpr); ok {
|
|
|
|
|
if idx, found := locals[ident.Name]; found {
|
|
|
|
|
upper := strings.ToUpper(ident.Name)
|
|
|
|
|
if idx, found := locals[upper]; found {
|
|
|
|
|
if pf.Op == token.INC {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.LocalAddInt(%d, 1)", idx))
|
|
|
|
|
} else {
|
|
|
|
|
@@ -695,6 +761,15 @@ func (g *Generator) emitExprStmt(s *ast.ExprStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// STATIC variable: s_nPass++
|
|
|
|
|
if goVar, found := g.staticVars[upper]; found {
|
|
|
|
|
delta := "1"
|
|
|
|
|
if pf.Op == token.DEC {
|
|
|
|
|
delta = "-1"
|
|
|
|
|
}
|
|
|
|
|
g.writeln(fmt.Sprintf("{ _v := %s.AsNumInt() + %s; %s = hbrt.MakeInt(int(_v)) }", goVar, delta, goVar))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Self field: ::field++
|
|
|
|
|
if send, ok := pf.X.(*ast.SendExpr); ok {
|
|
|
|
|
@@ -778,11 +853,11 @@ func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ident, ok := a.Left.(*ast.IdentExpr); ok {
|
|
|
|
|
if idx, found := locals[ident.Name]; found {
|
|
|
|
|
if idx, found := locals[strings.ToUpper(ident.Name)]; found {
|
|
|
|
|
switch a.Op {
|
|
|
|
|
case token.ASSIGN:
|
|
|
|
|
g.emitExpr(a.Right)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
|
|
|
case token.PLUSEQ:
|
|
|
|
|
g.emitExpr(a.Right)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
|
|
|
|
|
@@ -792,10 +867,10 @@ func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
|
|
|
|
|
default:
|
|
|
|
|
// General compound: push local, push right, op, pop local
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocal(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx))
|
|
|
|
|
g.emitExpr(a.Right)
|
|
|
|
|
g.emitBinaryOp(a.Op)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
@@ -1040,7 +1115,7 @@ func hasAppendInBody(stmts []ast.Stmt) bool {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
|
|
|
|
|
idx, found := locals[s.Var]
|
|
|
|
|
idx, found := locals[strings.ToUpper(s.Var)]
|
|
|
|
|
if !found {
|
|
|
|
|
g.writeln("// ERROR: FOR variable not found in locals")
|
|
|
|
|
return
|
|
|
|
|
@@ -1048,7 +1123,7 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
|
|
|
|
|
|
|
|
|
|
// i := start
|
|
|
|
|
g.emitExpr(s.Start)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocal(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
|
|
|
|
|
|
|
|
// Detect step direction for comparison
|
|
|
|
|
isNegStep := false
|
|
|
|
|
@@ -1095,7 +1170,7 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// General case: stack-based comparison
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocal(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx))
|
|
|
|
|
g.emitExpr(s.To)
|
|
|
|
|
if isNegStep {
|
|
|
|
|
g.writeln("t.GreaterEqual()")
|
|
|
|
|
@@ -1105,11 +1180,29 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
|
|
|
|
|
g.writeln("if !t.PopLogical() { break }")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Track FOR loop depth so LOOP can use goto instead of continue.
|
|
|
|
|
// Only emit label if LOOP is present in the body (Go rejects unused labels).
|
|
|
|
|
hasLoop := bodyHasLoop(s.Body)
|
|
|
|
|
forLabel := ""
|
|
|
|
|
prevForLabel := g.curForLabel
|
|
|
|
|
if hasLoop {
|
|
|
|
|
forLabel = fmt.Sprintf("_for_next_%d", g.forLabelSeq)
|
|
|
|
|
g.forLabelSeq++
|
|
|
|
|
g.curForLabel = forLabel
|
|
|
|
|
} else {
|
|
|
|
|
g.curForLabel = ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// body
|
|
|
|
|
for _, stmt := range s.Body {
|
|
|
|
|
g.emitStmt(stmt, locals)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Label for LOOP to jump to (skipping continue which would miss increment)
|
|
|
|
|
if hasLoop {
|
|
|
|
|
g.writeln(forLabel + ":")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// i += step (default 1)
|
|
|
|
|
if s.Step != nil {
|
|
|
|
|
g.emitExpr(s.Step)
|
|
|
|
|
@@ -1118,6 +1211,7 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.LocalAddInt(%d, 1)", idx))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.curForLabel = prevForLabel
|
|
|
|
|
g.indent--
|
|
|
|
|
g.writeln("}")
|
|
|
|
|
|
|
|
|
|
@@ -1129,6 +1223,69 @@ func (g *Generator) emitFor(s *ast.ForStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// bodyHasLoop checks if any statement in the body is a LOOP.
|
|
|
|
|
// Only checks the immediate level — LOOP inside nested FOR/DO WHILE is irrelevant.
|
|
|
|
|
func bodyHasLoop(stmts []ast.Stmt) bool {
|
|
|
|
|
for _, s := range stmts {
|
|
|
|
|
if hasLoopStmt(s) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func hasLoopStmt(s ast.Stmt) bool {
|
|
|
|
|
switch s := s.(type) {
|
|
|
|
|
case *ast.LoopStmt:
|
|
|
|
|
return true
|
|
|
|
|
case *ast.IfStmt:
|
|
|
|
|
for _, st := range s.Body {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, ei := range s.ElseIfs {
|
|
|
|
|
for _, st := range ei.Body {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, st := range s.ElseBody {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case *ast.SeqStmt:
|
|
|
|
|
for _, st := range s.Body {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, st := range s.RecoverBody {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case *ast.SwitchStmt:
|
|
|
|
|
for _, c := range s.Cases {
|
|
|
|
|
for _, st := range c.Body {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, st := range s.Otherwise {
|
|
|
|
|
if hasLoopStmt(st) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Do NOT recurse into ForStmt/DoWhileStmt — nested LOOP is for the inner loop
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitSwitch(s *ast.SwitchStmt, locals localMap) {
|
|
|
|
|
g.emitExpr(s.Expr)
|
|
|
|
|
g.writeln("_sw := t.Pop2()")
|
|
|
|
|
@@ -1193,8 +1350,8 @@ func (g *Generator) emitBeginSequence(s *ast.SeqStmt, locals localMap) {
|
|
|
|
|
g.writeln("if _seqErr != nil {")
|
|
|
|
|
g.indent++
|
|
|
|
|
if s.RecoverVar != "" {
|
|
|
|
|
if idx, found := locals[s.RecoverVar]; found {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocal(%d, hbrt.MakeString(_seqErr.Error()))", idx))
|
|
|
|
|
if idx, found := locals[strings.ToUpper(s.RecoverVar)]; found {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocalFast(%d, hbrt.MakeString(_seqErr.Error()))", idx))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, stmt := range s.RecoverBody {
|
|
|
|
|
@@ -1202,6 +1359,8 @@ func (g *Generator) emitBeginSequence(s *ast.SeqStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
g.indent--
|
|
|
|
|
g.writeln("}")
|
|
|
|
|
} else {
|
|
|
|
|
g.writeln("_ = _seqErr")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.indent--
|
|
|
|
|
@@ -1209,7 +1368,7 @@ func (g *Generator) emitBeginSequence(s *ast.SeqStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitForEach(s *ast.ForEachStmt, locals localMap) {
|
|
|
|
|
varIdx, found := locals[s.Var]
|
|
|
|
|
varIdx, found := locals[strings.ToUpper(s.Var)]
|
|
|
|
|
if !found {
|
|
|
|
|
g.writeln("// ERROR: FOR EACH variable not in locals")
|
|
|
|
|
return
|
|
|
|
|
@@ -1223,7 +1382,7 @@ func (g *Generator) emitForEach(s *ast.ForEachStmt, locals localMap) {
|
|
|
|
|
g.writeln("_feItems := _feArr.AsArray().Items")
|
|
|
|
|
g.writeln("for _feI := 0; _feI < len(_feItems); _feI++ {")
|
|
|
|
|
g.indent++
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocal(%d, _feItems[_feI])", varIdx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocalFast(%d, _feItems[_feI])", varIdx))
|
|
|
|
|
|
|
|
|
|
for _, stmt := range s.Body {
|
|
|
|
|
g.emitStmt(stmt, locals)
|
|
|
|
|
@@ -1255,7 +1414,7 @@ func (g *Generator) emitMultiAssign(s *ast.MultiAssignStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
idx := locals[strings.ToUpper(name)]
|
|
|
|
|
if idx > 0 {
|
|
|
|
|
g.writeln(fmt.Sprintf("if %d < len(_arr.Items) { t.SetLocal(%d, _arr.Items[%d]) }", i, idx, i))
|
|
|
|
|
g.writeln(fmt.Sprintf("if %d < len(_arr.Items) { t.SetLocalFast(%d, _arr.Items[%d]) }", i, idx, i))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
g.indent--
|
|
|
|
|
@@ -1265,7 +1424,7 @@ func (g *Generator) emitMultiAssign(s *ast.MultiAssignStmt, locals localMap) {
|
|
|
|
|
if s.Targets[0] != "_" {
|
|
|
|
|
idx := locals[strings.ToUpper(s.Targets[0])]
|
|
|
|
|
if idx > 0 {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocal(%d, _mr)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocalFast(%d, _mr)", idx))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
g.indent--
|
|
|
|
|
@@ -1285,7 +1444,7 @@ func (g *Generator) emitMultiAssign(s *ast.MultiAssignStmt, locals localMap) {
|
|
|
|
|
}
|
|
|
|
|
idx := locals[strings.ToUpper(name)]
|
|
|
|
|
if idx > 0 {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocal(%d, _mv%d)", idx, i))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocalFast(%d, _mv%d)", idx, i))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1308,16 +1467,33 @@ func (g *Generator) emitExpr(expr ast.Expr) {
|
|
|
|
|
case *ast.IdentExpr:
|
|
|
|
|
g.emitIdent(e)
|
|
|
|
|
case *ast.BinaryExpr:
|
|
|
|
|
g.emitExpr(e.Left)
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.emitBinaryOp(e.Op)
|
|
|
|
|
// Short-circuit AND/OR: Harbour evaluates right operand only if needed
|
|
|
|
|
if e.Op == token.AND {
|
|
|
|
|
g.emitExpr(e.Left)
|
|
|
|
|
g.writeln("if !t.PopLogical() {")
|
|
|
|
|
g.writeln("t.PushBool(false)")
|
|
|
|
|
g.writeln("} else {")
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln("}")
|
|
|
|
|
} else if e.Op == token.OR {
|
|
|
|
|
g.emitExpr(e.Left)
|
|
|
|
|
g.writeln("if t.PopLogical() {")
|
|
|
|
|
g.writeln("t.PushBool(true)")
|
|
|
|
|
g.writeln("} else {")
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln("}")
|
|
|
|
|
} else {
|
|
|
|
|
g.emitExpr(e.Left)
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.emitBinaryOp(e.Op)
|
|
|
|
|
}
|
|
|
|
|
case *ast.UnaryExpr:
|
|
|
|
|
g.emitExpr(e.X)
|
|
|
|
|
g.emitUnaryOp(e.Op)
|
|
|
|
|
case *ast.AssignExpr:
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln("t.Dup()")
|
|
|
|
|
g.writeln("// WARN: compound assignment — value on stack")
|
|
|
|
|
// Handle compound assignment (+=, -=, :=) in expression context.
|
|
|
|
|
// Needed for code block bodies like {|x| nSum += x}.
|
|
|
|
|
g.emitAssignExpr(e)
|
|
|
|
|
case *ast.CallExpr:
|
|
|
|
|
g.emitCall(e)
|
|
|
|
|
case *ast.DotExpr:
|
|
|
|
|
@@ -1391,7 +1567,7 @@ func (g *Generator) emitExpr(expr ast.Expr) {
|
|
|
|
|
// @variable — pass by reference
|
|
|
|
|
// In Five, we push a ByRef wrapper that holds the local index
|
|
|
|
|
if ident, ok := e.X.(*ast.IdentExpr); ok {
|
|
|
|
|
if idx, found := g.curLocals[ident.Name]; found {
|
|
|
|
|
if idx, found := g.curLocals[strings.ToUpper(ident.Name)]; found {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalRef(%d)", idx))
|
|
|
|
|
} else {
|
|
|
|
|
g.emitExpr(e.X) // fallback: push value
|
|
|
|
|
@@ -1519,8 +1695,8 @@ func (g *Generator) emitIdent(e *ast.IdentExpr) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if idx, found := g.curLocals[e.Name]; found {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocal(%d)", idx))
|
|
|
|
|
if idx, found := g.curLocals[upper]; found {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx))
|
|
|
|
|
} else if goVar, found := g.staticVars[upper]; found {
|
|
|
|
|
// Module-level STATIC variable
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushValue(%s)", goVar))
|
|
|
|
|
@@ -1545,6 +1721,13 @@ func (g *Generator) emitCall(e *ast.CallExpr) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
upper := strings.ToUpper(ident.Name)
|
|
|
|
|
// Guard: reserved words must never be emitted as function calls.
|
|
|
|
|
// This catches PRG bugs like stray ENDIF/ENDDO/NEXT from bad IF nesting.
|
|
|
|
|
if isReservedWord(upper) {
|
|
|
|
|
g.writeln(fmt.Sprintf("// WARN: reserved word %q used as function call — skipped", upper))
|
|
|
|
|
g.writeln("t.PushNil()")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if g.symCache != nil {
|
|
|
|
|
if varName, ok := g.symCache[upper]; ok {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushSymbol(%s)", varName))
|
|
|
|
|
@@ -1564,6 +1747,61 @@ func (g *Generator) emitCall(e *ast.CallExpr) {
|
|
|
|
|
g.writeln(fmt.Sprintf("t.Function(%d)", len(e.Args)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// emitAssignExpr handles := / += / -= in expression context (e.g. code block body).
|
|
|
|
|
func (g *Generator) emitAssignExpr(e *ast.AssignExpr) {
|
|
|
|
|
if ident, ok := e.Left.(*ast.IdentExpr); ok {
|
|
|
|
|
if idx, found := g.curLocals[strings.ToUpper(ident.Name)]; found {
|
|
|
|
|
switch e.Op {
|
|
|
|
|
case token.ASSIGN:
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln("t.Dup()")
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
|
|
|
case token.PLUSEQ:
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx))
|
|
|
|
|
case token.MINUSEQ:
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln("t.Negate()")
|
|
|
|
|
g.writeln(fmt.Sprintf("t.LocalAdd(%d)", idx))
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx))
|
|
|
|
|
default:
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushLocalFast(%d)", idx))
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.emitBinaryOp(e.Op)
|
|
|
|
|
g.writeln("t.Dup()")
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PopLocalFast(%d)", idx))
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Fallback: unknown target
|
|
|
|
|
g.emitExpr(e.Right)
|
|
|
|
|
g.writeln("t.Dup()")
|
|
|
|
|
g.writeln("// WARN: compound assignment — unknown target")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// isReservedWord returns true if the name is a Harbour reserved keyword
|
|
|
|
|
// that must never be emitted as a function call.
|
|
|
|
|
func isReservedWord(name string) bool {
|
|
|
|
|
switch name {
|
|
|
|
|
case "IF", "ELSE", "ELSEIF", "ENDIF",
|
|
|
|
|
"DO", "WHILE", "ENDDO",
|
|
|
|
|
"FOR", "NEXT", "TO", "STEP",
|
|
|
|
|
"RETURN", "FUNCTION", "PROCEDURE",
|
|
|
|
|
"LOCAL", "STATIC", "PRIVATE", "PUBLIC",
|
|
|
|
|
"BEGIN", "SEQUENCE", "RECOVER", "END",
|
|
|
|
|
"SWITCH", "CASE", "OTHERWISE", "ENDCASE",
|
|
|
|
|
"EXIT", "LOOP",
|
|
|
|
|
"CLASS", "ENDCLASS", "METHOD", "DATA",
|
|
|
|
|
"WITH", "OBJECT",
|
|
|
|
|
"NIL", "TRUE", "FALSE",
|
|
|
|
|
"AND", "OR", "NOT", "IN":
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// tryEmitInlineRTL emits direct Go code for known RTL functions.
|
|
|
|
|
// Returns true if handled (no VM dispatch needed).
|
|
|
|
|
// This eliminates Frame/EndProc/symbol lookup for hot-path functions.
|
|
|
|
|
@@ -1866,16 +2104,42 @@ type goFastEntry struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitAliasExpr(e *ast.AliasExpr) {
|
|
|
|
|
// alias->field or (expr)->field
|
|
|
|
|
// Push alias, then field name, call runtime FieldGet by name
|
|
|
|
|
if ident, ok := e.Alias.(*ast.IdentExpr); ok {
|
|
|
|
|
// Static alias: customers->name
|
|
|
|
|
g.writeln(fmt.Sprintf(`t.PushAliasField(%q, %q)`, ident.Name, g.fieldName(e.Field)))
|
|
|
|
|
} else {
|
|
|
|
|
// Dynamic: (cAlias)->field
|
|
|
|
|
g.emitExpr(e.Alias)
|
|
|
|
|
g.writeln(fmt.Sprintf(`t.PushDynAliasField(t.Pop2().AsString(), %q)`, g.fieldName(e.Field)))
|
|
|
|
|
fieldIdent, isFieldIdent := e.Field.(*ast.IdentExpr)
|
|
|
|
|
|
|
|
|
|
// Case 1: alias->field (static alias, simple field name)
|
|
|
|
|
if ident, ok := e.Alias.(*ast.IdentExpr); ok && isFieldIdent {
|
|
|
|
|
g.writeln(fmt.Sprintf(`t.PushAliasField(%q, %q)`, ident.Name, fieldIdent.Name))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Case 2: (expr)->field (dynamic alias, simple field name)
|
|
|
|
|
if isFieldIdent {
|
|
|
|
|
g.emitExpr(e.Alias)
|
|
|
|
|
g.writeln(fmt.Sprintf(`t.PushDynAliasField(t.Pop2().AsString(), %q)`, fieldIdent.Name))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Case 3: alias->(expr) or (expr)->(expr) — workarea context expression
|
|
|
|
|
// Harbour: save current WA, select new WA, evaluate expr, restore WA
|
|
|
|
|
// Example: (nArea)->(Used()) → evaluate Used() in workarea nArea
|
|
|
|
|
// Example: CUSTOMERS->(RecCount()) → evaluate RecCount() in CUSTOMERS workarea
|
|
|
|
|
if ident, ok := e.Alias.(*ast.IdentExpr); ok {
|
|
|
|
|
_, isLocal := g.curLocals[strings.ToUpper(ident.Name)]
|
|
|
|
|
if isLocal {
|
|
|
|
|
// Local variable: emit value (numeric area number)
|
|
|
|
|
g.emitExpr(e.Alias)
|
|
|
|
|
g.writeln(`t.WASaveAndSelect(int(t.Pop2().AsNumInt()))`)
|
|
|
|
|
} else {
|
|
|
|
|
// Static alias name: resolve by alias string
|
|
|
|
|
g.writeln(fmt.Sprintf(`t.WASaveAndSelectAlias(%q)`, ident.Name))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Dynamic: numeric area from expression
|
|
|
|
|
g.emitExpr(e.Alias)
|
|
|
|
|
g.writeln(`t.WASaveAndSelect(int(t.Pop2().AsNumInt()))`)
|
|
|
|
|
}
|
|
|
|
|
g.emitExpr(e.Field)
|
|
|
|
|
g.writeln(`t.WARestore()`)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Generator) fieldName(expr ast.Expr) string {
|
|
|
|
|
@@ -1947,30 +2211,132 @@ func (g *Generator) emitSendExpr(e *ast.SendExpr) {
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitBlock(e *ast.BlockExpr) {
|
|
|
|
|
// Code block: {|params| body}
|
|
|
|
|
// The block function receives the SAME thread (t), not a new one.
|
|
|
|
|
// Block params are passed via Frame() from Eval/AEval.
|
|
|
|
|
nParams := len(e.Params)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushBlock(func(t *hbrt.Thread) {"))
|
|
|
|
|
g.indent++
|
|
|
|
|
g.writeln(fmt.Sprintf("t.Frame(%d, 0)", nParams))
|
|
|
|
|
g.writeln("defer t.EndProc()")
|
|
|
|
|
|
|
|
|
|
// Build local map for block params
|
|
|
|
|
oldLocals := g.curLocals
|
|
|
|
|
// Collect free variables in the block body that reference outer locals.
|
|
|
|
|
// These need to be captured via Go closure variables.
|
|
|
|
|
outerLocals := g.curLocals
|
|
|
|
|
blockLocals := make(localMap)
|
|
|
|
|
for i, p := range e.Params {
|
|
|
|
|
blockLocals[p] = i + 1
|
|
|
|
|
blockLocals[strings.ToUpper(p)] = i + 1
|
|
|
|
|
}
|
|
|
|
|
g.curLocals = blockLocals
|
|
|
|
|
|
|
|
|
|
// Find all idents in block body that are in outerLocals but NOT in blockLocals
|
|
|
|
|
freeVars := g.collectFreeVars(e.Body, blockLocals, outerLocals)
|
|
|
|
|
|
|
|
|
|
// Harbour: closures share outer locals via RefCell (mutable capture).
|
|
|
|
|
// Convert each captured outer local to a RefCell, then pass the RefCell
|
|
|
|
|
// into the block. Both outer function and block read/write through it.
|
|
|
|
|
for _, fv := range freeVars {
|
|
|
|
|
outerIdx := outerLocals[fv]
|
|
|
|
|
// Ensure outer local is a RefCell (PushLocalRef creates one if needed,
|
|
|
|
|
// but we do it inline to avoid stack ops).
|
|
|
|
|
g.writeln(fmt.Sprintf("t.EnsureLocalRef(%d) // share %s via RefCell", outerIdx, fv))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Capture the RefCell values with unique names to avoid Go scope issues.
|
|
|
|
|
capSeq := g.blockSeq
|
|
|
|
|
g.blockSeq++
|
|
|
|
|
capNames := make(map[string]string) // fv → Go var name
|
|
|
|
|
for _, fv := range freeVars {
|
|
|
|
|
outerIdx := outerLocals[fv]
|
|
|
|
|
capName := fmt.Sprintf("_cap_%s_%d", fv, capSeq)
|
|
|
|
|
g.writeln(fmt.Sprintf("%s := t.LocalRaw(%d) // capture RefCell %s", capName, outerIdx, fv))
|
|
|
|
|
capNames[fv] = capName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.writeln(fmt.Sprintf("t.PushBlock(func(t *hbrt.Thread) {"))
|
|
|
|
|
g.indent++
|
|
|
|
|
nLocals := len(freeVars)
|
|
|
|
|
g.writeln(fmt.Sprintf("t.Frame(%d, %d)", nParams, nLocals))
|
|
|
|
|
g.writeln("defer t.EndProc()")
|
|
|
|
|
|
|
|
|
|
// Inject RefCell values directly into block locals — reads/writes go through RefCell
|
|
|
|
|
for i, fv := range freeVars {
|
|
|
|
|
localIdx := nParams + i + 1
|
|
|
|
|
blockLocals[fv] = localIdx
|
|
|
|
|
g.writeln(fmt.Sprintf("t.SetLocalRaw(%d, %s) // inject shared RefCell %s", localIdx, capNames[fv], fv))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
g.curLocals = blockLocals
|
|
|
|
|
g.emitExpr(e.Body)
|
|
|
|
|
g.writeln("t.RetValue()")
|
|
|
|
|
|
|
|
|
|
g.curLocals = oldLocals
|
|
|
|
|
g.curLocals = outerLocals
|
|
|
|
|
g.indent--
|
|
|
|
|
g.writeln(fmt.Sprintf("}, %d)", 0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// collectFreeVars finds identifier names in expr that exist in outerLocals but not blockLocals.
|
|
|
|
|
func (g *Generator) collectFreeVars(expr ast.Expr, blockLocals, outerLocals localMap) []string {
|
|
|
|
|
var result []string
|
|
|
|
|
seen := map[string]bool{}
|
|
|
|
|
g.walkExprIdents(expr, func(name string) {
|
|
|
|
|
upper := strings.ToUpper(name)
|
|
|
|
|
if seen[upper] {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if _, inBlock := blockLocals[upper]; inBlock {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if _, inOuter := outerLocals[upper]; inOuter {
|
|
|
|
|
seen[upper] = true
|
|
|
|
|
result = append(result, upper)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// walkExprIdents calls fn for each IdentExpr in the expression tree.
|
|
|
|
|
func (g *Generator) walkExprIdents(expr ast.Expr, fn func(string)) {
|
|
|
|
|
if expr == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
switch e := expr.(type) {
|
|
|
|
|
case *ast.IdentExpr:
|
|
|
|
|
fn(e.Name)
|
|
|
|
|
case *ast.BinaryExpr:
|
|
|
|
|
g.walkExprIdents(e.Left, fn)
|
|
|
|
|
g.walkExprIdents(e.Right, fn)
|
|
|
|
|
case *ast.UnaryExpr:
|
|
|
|
|
g.walkExprIdents(e.X, fn)
|
|
|
|
|
case *ast.PostfixExpr:
|
|
|
|
|
g.walkExprIdents(e.X, fn)
|
|
|
|
|
case *ast.CallExpr:
|
|
|
|
|
g.walkExprIdents(e.Func, fn)
|
|
|
|
|
for _, a := range e.Args {
|
|
|
|
|
g.walkExprIdents(a, fn)
|
|
|
|
|
}
|
|
|
|
|
case *ast.IndexExpr:
|
|
|
|
|
g.walkExprIdents(e.X, fn)
|
|
|
|
|
g.walkExprIdents(e.Index, fn)
|
|
|
|
|
case *ast.DotExpr:
|
|
|
|
|
g.walkExprIdents(e.X, fn)
|
|
|
|
|
case *ast.AssignExpr:
|
|
|
|
|
g.walkExprIdents(e.Left, fn)
|
|
|
|
|
g.walkExprIdents(e.Right, fn)
|
|
|
|
|
case *ast.ArrayLitExpr:
|
|
|
|
|
for _, item := range e.Items {
|
|
|
|
|
g.walkExprIdents(item, fn)
|
|
|
|
|
}
|
|
|
|
|
case *ast.IIfExpr:
|
|
|
|
|
g.walkExprIdents(e.Cond, fn)
|
|
|
|
|
g.walkExprIdents(e.True, fn)
|
|
|
|
|
g.walkExprIdents(e.False, fn)
|
|
|
|
|
case *ast.SendExpr:
|
|
|
|
|
g.walkExprIdents(e.Object, fn)
|
|
|
|
|
for _, a := range e.Args {
|
|
|
|
|
g.walkExprIdents(a, fn)
|
|
|
|
|
}
|
|
|
|
|
case *ast.AliasExpr:
|
|
|
|
|
g.walkExprIdents(e.Alias, fn)
|
|
|
|
|
g.walkExprIdents(e.Field, fn)
|
|
|
|
|
case *ast.BlockExpr:
|
|
|
|
|
g.walkExprIdents(e.Body, fn)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (g *Generator) emitBinaryOp(op token.Kind) {
|
|
|
|
|
switch op {
|
|
|
|
|
case token.PLUS:
|
|
|
|
|
|