feat: SET DELETED filtering, SEEK/LOCATE/CONTINUE, SET command codegen

- skipFilter: skip deleted records in GoTop/GoBottom/Skip when SET DELETED ON
- hbrdd.IsSetDeleted callback: avoids circular import hbrdd→hbrtl
- Parser: capture ON/OFF for boolean SET commands (DELETED, EXACT, SOFTSEEK, etc.)
- Parser: capture TO expr for SET DATE/DECIMALS/EPOCH
- Gengo: emit proper t.Do() calls for 11 SET toggles + 3 value SETs
- stmtSet: was stub (skipToEOL), now calls parseSet()
- RTL: register 11 SET toggle functions (SETDELETED, SETEXACT, etc.)
- RTL: DBLOCATE/DBCONTINUE for sequential search
- RTL: DBSETFILTER/DBCLEARFILTER/DBFILTER
- PadL/PadR: support 3rd param fill character
- Area interface: added SetFound, SetLocate, LocateBlock, filter methods
- MemRDD: implements new Area interface methods
- Comprehensive PRG test: test_search.prg (7 test suites all pass)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-02 22:33:59 +09:00
parent 08c0ef13d4
commit 21fd9dc65c
12 changed files with 651 additions and 30 deletions

View File

@@ -491,23 +491,64 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) {
g.indent--
g.writeln("}")
case *ast.SetCmd:
upper := strings.ToUpper(s.Setting)
// Boolean SET toggles — call RTL Set function, no workarea needed
setFuncMap := map[string]string{
"DELETED": "SETDELETED",
"EXACT": "SETEXACT",
"SOFTSEEK": "SETSOFTSEEK",
"EXCLUSIVE": "SETEXCLUSIVE",
"FIXED": "SETFIXED",
"CANCEL": "SETCANCEL",
"BELL": "SETBELL",
"CONFIRM": "SETCONFIRM",
"INSERT": "SETINSERT",
"ESCAPE": "SETESCAPE",
"WRAP": "SETWRAP",
}
if funcName, ok := setFuncMap[upper]; ok {
onOff := strings.ToUpper(s.Extra)
if onOff == "ON" || onOff == "OFF" {
val := "true"
if onOff == "OFF" {
val = "false"
}
g.writeln(fmt.Sprintf(`t.PushSymbol(t.VM().FindSymbol(%q))`, funcName))
g.writeln("t.PushNil()")
g.writeln(fmt.Sprintf("t.PushBool(%s)", val))
g.writeln("t.Do(1)")
}
break
}
// Value SET commands — SET DATE/DECIMALS/EPOCH TO expr
valueFuncMap := map[string]string{
"DATE": "__SETDATEFORMAT",
"DECIMALS": "SETDECIMALS",
"EPOCH": "SETEPOCH",
}
if funcName, ok := valueFuncMap[upper]; ok && s.Expr != nil {
g.writeln(fmt.Sprintf(`t.PushSymbol(t.VM().FindSymbol(%q))`, funcName))
g.writeln("t.PushNil()")
g.emitExpr(s.Expr)
g.writeln("t.Do(1)")
break
}
// Workarea-specific SET commands
g.writeln("{")
g.indent++
g.writeln("wa := t.WA.(*hbrdd.WorkAreaManager)")
g.writeln("if area := wa.Current(); area != nil {")
g.indent++
upper := strings.ToUpper(s.Setting)
switch upper {
case "FILTER":
if s.Expr != nil {
g.writeln("if flt, ok := area.(hbrdd.Filterer); ok {")
g.indent++
g.emitExpr(s.Expr)
g.writeln(`flt.SetFilter(t.Pop2().AsString(), nil)`)
g.indent--
g.writeln("}")
g.writeln(`area.SetFilter(t.Pop2().AsString(), nil)`)
} else {
g.writeln("if flt, ok := area.(hbrdd.Filterer); ok { flt.ClearFilter() }")
g.writeln("area.ClearFilter()")
}
case "ORDER":
if s.Expr != nil {

View File

@@ -1706,10 +1706,23 @@ func (p *Parser) parseSet() *ast.SetCmd {
var extra string
// SET commands: consume everything until end of line.
// Values like "GR+/B, W+/BG" can't be parsed as expressions.
// SET FILTER TO is special — the condition IS an expression.
// Boolean toggles: SET DELETED ON/OFF, SET EXACT ON/OFF, etc.
// Value settings: SET FILTER TO expr, SET ORDER TO n, SET DATE TO fmt
upperSetting := strings.ToUpper(setting)
if p.match(token.TO) {
// Check for ON/OFF boolean toggle
booleanSets := map[string]bool{
"DELETED": true, "EXACT": true, "SOFTSEEK": true, "EXCLUSIVE": true,
"FIXED": true, "CANCEL": true, "BELL": true, "CONFIRM": true,
"INSERT": true, "ESCAPE": true, "WRAP": true, "INTENSITY": true,
"SCOREBOARD": true, "CONSOLE": true, "ALTERNATE": true, "PRINTER": true,
}
if booleanSets[upperSetting] {
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
extra = strings.ToUpper(p.expectMethodName().Literal)
}
} else if p.match(token.TO) {
if upperSetting == "FILTER" || upperSetting == "RELATION" || upperSetting == "ORDER" || upperSetting == "INDEX" {
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
expr = p.parseExpr()
@@ -1718,6 +1731,10 @@ func (p *Parser) parseSet() *ast.SetCmd {
p.advance()
extra = p.expectMethodName().Literal
}
} else if upperSetting == "DATE" || upperSetting == "DECIMALS" || upperSetting == "EPOCH" {
if p.current.Kind != token.NEWLINE && p.current.Kind != token.EOF {
expr = p.parseExpr()
}
}
}

View File

@@ -241,10 +241,7 @@ func (p *Parser) stmtRecallPackZap() ast.Stmt {
}
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"}}
return p.parseSet()
}
func (p *Parser) stmtDefer() ast.Stmt { return p.parseDefer() }