diff --git a/compiler/analyzer/analyzer.go b/compiler/analyzer/analyzer.go index 8d806cc..7057e4d 100644 --- a/compiler/analyzer/analyzer.go +++ b/compiler/analyzer/analyzer.go @@ -467,7 +467,7 @@ var rtlFunctions = map[string]bool{ "TYPE": true, "PCOUNT": true, "BREAK": true, "ARRAY": true, "FCOUNT": true, "FIELDNAME": true, "SELECT": true, "FILE": true, "INKEY": true, "TRANSFORM": true, "SETDATEFORMAT": true, "SETEPOCH": true, "SETCENTURY": true, - "IIF": true, "IF": true, "STRZERO": true, "OUTSTD": true, + "IIF": true, "IF": true, "STRZERO": true, "OUTSTD": true, "OUTERR": true, "CENTER": true, "SOUNDEX": true, "TONE": true, // Terminal "SETPOS": true, "ROW": true, "COL": true, "DEVPOS": true, "DEVOUT": true, @@ -634,6 +634,8 @@ func (a *Analyzer) isBuiltinConstant(name string) bool { "SELF": true, "SUPER": true, // Harbour commands treated as identifiers "QUIT": true, "ERRORLEVEL": true, + // Field/Memvar alias prefixes + "FIELD": true, "_FIELD": true, "M": true, "MEMVAR": true, // Keyboard constants "K_ESC": true, "K_ENTER": true, "K_UP": true, "K_DOWN": true, "K_LEFT": true, "K_RIGHT": true, "K_PGUP": true, "K_PGDN": true, diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index 9669032..c4672ee 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -852,6 +852,17 @@ func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) { } } + // Check for alias->field := value (FIELD->NAME := value) + if aliasExpr, ok := a.Left.(*ast.AliasExpr); ok { + if aliasIdent, ok2 := aliasExpr.Alias.(*ast.IdentExpr); ok2 { + if fieldIdent, ok3 := aliasExpr.Field.(*ast.IdentExpr); ok3 { + g.emitExpr(a.Right) + g.writeln(fmt.Sprintf(`{ _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField(%q, %q, t.Pop2()) }`, aliasIdent.Name, fieldIdent.Name)) + return + } + } + } + if ident, ok := a.Left.(*ast.IdentExpr); ok { if idx, found := locals[strings.ToUpper(ident.Name)]; found { switch a.Op { @@ -1749,6 +1760,17 @@ func (g *Generator) emitCall(e *ast.CallExpr) { // emitAssignExpr handles := / += / -= in expression context (e.g. code block body). func (g *Generator) emitAssignExpr(e *ast.AssignExpr) { + // alias->field := value in expression context + if aliasExpr, ok := e.Left.(*ast.AliasExpr); ok { + if aliasIdent, ok2 := aliasExpr.Alias.(*ast.IdentExpr); ok2 { + if fieldIdent, ok3 := aliasExpr.Field.(*ast.IdentExpr); ok3 { + g.emitExpr(e.Right) + g.writeln("t.Dup()") + g.writeln(fmt.Sprintf(`{ _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField(%q, %q, t.Pop2()) }`, aliasIdent.Name, fieldIdent.Name)) + return + } + } + } if ident, ok := e.Left.(*ast.IdentExpr); ok { if idx, found := g.curLocals[strings.ToUpper(ident.Name)]; found { switch e.Op { diff --git a/hbrdd/cdx/cdx.go b/hbrdd/cdx/cdx.go index 34d3ba8..39cdfc1 100644 --- a/hbrdd/cdx/cdx.go +++ b/hbrdd/cdx/cdx.go @@ -22,6 +22,7 @@ import ( "encoding/binary" "fmt" "os" + "sort" "strings" "syscall" ) @@ -376,6 +377,14 @@ func OpenIndex(path string) (*Index, error) { // points to the tag header at a specific file offset. tagEntries := readCompoundTagList(idx, rootHdr) + // Harbour orders tags by file offset (TagBlock) ascending, which + // corresponds to creation order. The compound B-tree stores entries + // alphabetically, so we must re-sort by offset to match Harbour. + // See: hb_cdxIndexLoadAvailTags in dbfcdx1.c + sort.Slice(tagEntries, func(i, j int) bool { + return tagEntries[i].offset < tagEntries[j].offset + }) + for _, entry := range tagEntries { tagHdr, err := ReadTagHeader(f, entry.offset) if err != nil { diff --git a/hbrdd/workarea.go b/hbrdd/workarea.go index 9bd0939..3c17ab7 100644 --- a/hbrdd/workarea.go +++ b/hbrdd/workarea.go @@ -9,6 +9,7 @@ package hbrdd import ( + "five/hbrt" "fmt" "strings" ) @@ -213,11 +214,18 @@ func (wm *WorkAreaManager) CloseAll() { } // GetAliasField returns a field value from a named alias. -// Used by alias->field syntax. -func (wm *WorkAreaManager) GetAliasField(alias, field string) interface{} { - area := wm.ByAlias(alias) +// Used by alias->field syntax (e.g., CUSTOMERS->NAME, FIELD->AGE). +func (wm *WorkAreaManager) GetAliasField(alias, field string) hbrt.Value { + var area Area + + // FIELD-> is a special alias meaning "current workarea" + if strings.EqualFold(alias, "FIELD") || strings.EqualFold(alias, "_FIELD") { + area = wm.Current() + } else { + area = wm.ByAlias(alias) + } if area == nil { - return nil + return hbrt.MakeNil() } // Find field by name for i := 0; i < area.FieldCount(); i++ { @@ -227,7 +235,28 @@ func (wm *WorkAreaManager) GetAliasField(alias, field string) interface{} { return val } } - return nil + return hbrt.MakeNil() +} + +// SetAliasField sets a field value by alias->field syntax. +func (wm *WorkAreaManager) SetAliasField(alias, field string, val hbrt.Value) { + var area Area + + if strings.EqualFold(alias, "FIELD") || strings.EqualFold(alias, "_FIELD") { + area = wm.Current() + } else { + area = wm.ByAlias(alias) + } + if area == nil { + return + } + for i := 0; i < area.FieldCount(); i++ { + fi := area.GetFieldInfo(i) + if strings.EqualFold(fi.Name, field) { + area.PutValue(i, val) + return + } + } } // --- Helpers --- diff --git a/hbrtl/console.go b/hbrtl/console.go index d9d3dad..598ae81 100644 --- a/hbrtl/console.go +++ b/hbrtl/console.go @@ -8,6 +8,7 @@ package hbrtl import ( "five/hbrt" "fmt" + "os" "strings" ) @@ -83,4 +84,30 @@ func valueToDisplay(v hbrt.Value) string { } } +// rtlOutStd writes values to stdout without newline. Harbour: OutStd() +func rtlOutStd(t *hbrt.Thread) { + nParams := t.ParamCount() + t.Frame(nParams, 0) + defer t.EndProcFast() + parts := make([]string, nParams) + for i := 0; i < nParams; i++ { + parts[i] = valueToDisplay(t.Local(i + 1)) + } + fmt.Print(strings.Join(parts, "")) + t.RetNil() +} + +// rtlOutErr writes values to stderr without newline. Harbour: OutErr() +func rtlOutErr(t *hbrt.Thread) { + nParams := t.ParamCount() + t.Frame(nParams, 0) + defer t.EndProcFast() + parts := make([]string, nParams) + for i := 0; i < nParams; i++ { + parts[i] = valueToDisplay(t.Local(i + 1)) + } + fmt.Fprint(os.Stderr, strings.Join(parts, "")) + t.RetNil() +} + // julianToDateStr and date formatting moved to datetime.go diff --git a/hbrtl/indexrtl.go b/hbrtl/indexrtl.go index 6f91cf7..9fbbca9 100644 --- a/hbrtl/indexrtl.go +++ b/hbrtl/indexrtl.go @@ -11,6 +11,7 @@ import ( "five/hbrt" "five/hbrdd" "five/hbrdd/dbf" + "fmt" ) // INDEXORD() → nCurrentOrder (1-based, 0 = natural) @@ -74,8 +75,8 @@ func OrdSetFocus(t *hbrt.Thread) { if idx, ok := area.(hbrdd.Indexer); ok { v := t.Local(1) if v.IsNumeric() { - // SET ORDER TO n - idx.OrderListFocus(v.AsString()) + // SET ORDER TO n — convert number to digit string for OrderListFocus + idx.OrderListFocus(fmt.Sprintf("%d", v.AsNumInt())) } else { idx.OrderListFocus(v.AsString()) } diff --git a/hbrtl/register.go b/hbrtl/register.go index 8c5c661..a88bf77 100644 --- a/hbrtl/register.go +++ b/hbrtl/register.go @@ -24,6 +24,8 @@ func RegisterRTL(vm *hbrt.VM) { // Console hbrt.Sym("QOUT", hbrt.FsPublic, rtlQOut), hbrt.Sym("QQOUT", hbrt.FsPublic, rtlQQOut), + hbrt.Sym("OUTSTD", hbrt.FsPublic, rtlOutStd), + hbrt.Sym("OUTERR", hbrt.FsPublic, rtlOutErr), // Strings / Conversion hbrt.Sym("STR", hbrt.FsPublic, Str),