diff --git a/compiler/gengo/gengo.go b/compiler/gengo/gengo.go index 6c9e532..f492124 100644 --- a/compiler/gengo/gengo.go +++ b/compiler/gengo/gengo.go @@ -555,7 +555,7 @@ func (g *Generator) emitStmt(stmt ast.Stmt, locals localMap) { g.writeln("if idx, ok := area.(hbrdd.Indexer); ok {") g.indent++ g.emitExpr(s.Expr) - g.writeln(`idx.OrderListFocus(t.Pop2().AsString())`) + g.writeln(`{ _ov := t.Pop2(); var _os string; if _ov.IsNumeric() { _os = hbrt.NtoS(_ov.AsNumInt()) } else { _os = _ov.AsString() }; idx.OrderListFocus(_os) }`) g.indent-- g.writeln("}") } diff --git a/examples/cross_cdx_test.prg b/examples/cross_cdx_test.prg new file mode 100644 index 0000000..1151c21 --- /dev/null +++ b/examples/cross_cdx_test.prg @@ -0,0 +1,87 @@ +// Five reads Harbour-created CDX — binary compatibility + ORDSCOPE test +PROCEDURE Main() + LOCAL nCount + + // Open Harbour-created DBF + CDX + USE "/tmp/cdx_test" NEW + SET INDEX TO "/tmp/cdx_test.cdx" + + ? "RECORDS=" + LTrim(Str(RecCount())) + ? "ORDCOUNT=" + LTrim(Str(OrdCount())) + + // NAME tag + OrdSetFocus("BYNAME") + GO TOP + ? "N_TOP=" + RTrim(FieldGet(2)) + " " + LTrim(Str(FieldGet(1))) + + SEEK PadR("Name_001", 20) + ? "N_S001=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + SEEK PadR("Name_025", 20) + ? "N_S025=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + SEEK PadR("Name_050", 20) + ? "N_S050=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + SEEK "Name_01" + ? "N_P01=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + + GO BOTTOM + ? "N_BOTTOM=" + RTrim(FieldGet(2)) + " " + LTrim(Str(RecNo())) + + // CITY tag + OrdSetFocus("BYCITY") + GO TOP + ? "C_TOP=" + RTrim(FieldGet(3)) + " " + LTrim(Str(FieldGet(1))) + SEEK PadR("Seoul", 15) + ? "C_SEOUL=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + SEEK PadR("Tokyo", 15) + ? "C_TOKYO=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + + // ORDSCOPE on CITY: London..Seoul + OrdScope(0, PadR("London", 15)) + OrdScope(1, PadR("Seoul", 15)) + + GO TOP + ? "SCOPE_TOP=" + RTrim(FieldGet(3)) + " " + LTrim(Str(RecNo())) + GO BOTTOM + ? "SCOPE_BOT=" + RTrim(FieldGet(3)) + " " + LTrim(Str(RecNo())) + + GO TOP + nCount := 0 + DO WHILE !EOF() + nCount++ + SKIP + ENDDO + ? "SCOPE_CNT=" + LTrim(Str(nCount)) + + // Clear scope + OrdScope(0, NIL) + OrdScope(1, NIL) + GO TOP + nCount := 0 + DO WHILE !EOF() + nCount++ + SKIP + ENDDO + ? "NOSCOPE_CNT=" + LTrim(Str(nCount)) + + // ID tag + OrdSetFocus("BYID") + SEEK Str(10, 6) + ? "I_S10=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + SEEK Str(40, 6) + ? "I_S40=" + IIF(Found(),".T.",".F.") + " " + LTrim(Str(RecNo())) + + // ORDSCOPE on ID: 20..30 + OrdScope(0, Str(20, 6)) + OrdScope(1, Str(30, 6)) + GO TOP + nCount := 0 + DO WHILE !EOF() + nCount++ + SKIP + ENDDO + ? "ID_SCOPE_CNT=" + LTrim(Str(nCount)) + OrdScope(0, NIL) + OrdScope(1, NIL) + + CLOSE ALL +RETURN diff --git a/hbrdd/cdx/cdx.go b/hbrdd/cdx/cdx.go index 7540dd1..37b5509 100644 --- a/hbrdd/cdx/cdx.go +++ b/hbrdd/cdx/cdx.go @@ -379,6 +379,9 @@ func (idx *Index) GetTag(i int) *Tag { return nil } +// Tags returns all tags in the CDX. +func (idx *Index) Tags() []*Tag { return idx.tags } + // FindTag returns a tag by name. func (idx *Index) FindTag(name string) *Tag { upper := strings.ToUpper(name) @@ -498,12 +501,27 @@ func scanCompoundLeaves(f *os.File, rootHdr *TagHeader) []tagDirEntry { } // decodeCompoundLeaf decodes tag entries from a compound leaf page. +// Compound index uses the same bit-packed format as data leaves, +// with keyLen=10 (tag name) and recNo = page offset / PageLen. func decodeCompoundLeaf(data []byte, nKeys int) []tagDirEntry { + if nKeys <= 0 || len(data) < ExtHeadSize { + return nil + } + + // Use the standard leaf key decoder with keyLen=10 (compound tag name size) + hdr := DecodeLeafHeader(data) + keys := DecodeLeafKeys(data, hdr, 10) + var entries []tagDirEntry - // Compound index leaf format is simpler than data index - // Each entry: offset varies by CDX implementation - // For now return empty — scanCompoundLeaves handles it - _ = nKeys + for _, dk := range keys { + name := trimNull(dk.Key) + name = strings.TrimSpace(name) + if name == "" { + continue + } + // RecNo in compound index = direct byte offset to tag header + entries = append(entries, tagDirEntry{name: name, offset: int64(dk.RecNo)}) + } return entries } @@ -795,6 +813,21 @@ func (t *Tag) IsEOF() bool { return t.tagEOF } // IsBOF returns true if before start. func (t *Tag) IsBOF() bool { return t.tagBOF } +// KeyLen returns the key length. +func (t *Tag) KeyLen() int { return t.keyLen } + +// KeyExpr returns the key expression string stored in the CDX header. +func (t *Tag) KeyExpr() string { return t.header.KeyExpr } + +// ForExpr returns the FOR condition expression. +func (t *Tag) ForExpr() string { return t.header.ForExpr } + +// IsDescending returns true if the tag sorts in descending order. +func (t *Tag) IsDescending() bool { return t.header.Descending } + +// Close is a no-op for tags (the parent Index owns the file). +func (t *Tag) Close() error { return nil } + // --- Helpers --- func trimNull(b []byte) string { diff --git a/hbrdd/dbf/indexer.go b/hbrdd/dbf/indexer.go index 7ae0bcf..b4df5d2 100644 --- a/hbrdd/dbf/indexer.go +++ b/hbrdd/dbf/indexer.go @@ -10,6 +10,7 @@ import ( "bytes" "five/hbrt" "five/hbrdd" + "five/hbrdd/cdx" "five/hbrdd/ntx" "fmt" "os" @@ -18,13 +19,31 @@ import ( "strings" ) +// IndexEngine is the common interface for NTX Index and CDX Tag. +type IndexEngine interface { + Seek(searchKey []byte) (uint32, bool) + GoTop() bool + GoBottom() bool + SkipNext() bool + SkipPrev() bool + CurRecNo() uint32 + CurKey() []byte + IsEOF() bool + IsBOF() bool + KeyLen() int + Close() error +} + // indexState holds active index state for a DBFArea. type indexState struct { - indexes []*ntx.Index // open NTX index files + indexes []IndexEngine // open NTX/CDX index engines names []string // index file paths tags []string // tag names (for display) current int // active index (-1 = natural order) keyExprs []string // key expressions for each index + // Scope support + scopeTop []byte // top scope key (nil = no scope) + scopeBottom []byte // bottom scope key (nil = no scope) } // ensureIndexState initializes the index state if nil. @@ -117,14 +136,40 @@ func (a *DBFArea) OrderCreate(params hbrdd.OrderCreateParams) error { return nil } -// OrderListAdd opens an existing index file. +// OrderListAdd opens an existing index file (NTX single-order or CDX compound). func (a *DBFArea) OrderListAdd(path string) error { a.ensureIndexState() + // Auto-detect extension: try .cdx first, then .ntx if !strings.Contains(filepath.Base(path), ".") { - path += ".ntx" + if _, err := os.Stat(path + ".cdx"); err == nil { + path += ".cdx" + } else { + path += ".ntx" + } } + ext := strings.ToLower(filepath.Ext(path)) + + if ext == ".cdx" { + // CDX compound index — opens all tags + ci, err := cdx.OpenIndex(path) + if err != nil { + return fmt.Errorf("open CDX failed: %w", err) + } + for _, tag := range ci.Tags() { + a.idxState.indexes = append(a.idxState.indexes, tag) + a.idxState.names = append(a.idxState.names, path) + a.idxState.tags = append(a.idxState.tags, tag.Name) + a.idxState.keyExprs = append(a.idxState.keyExprs, tag.KeyExpr()) + } + if len(ci.Tags()) > 0 { + a.idxState.current = len(a.idxState.indexes) - len(ci.Tags()) // first tag + } + return nil + } + + // NTX single index idx, err := ntx.OpenIndex(path) if err != nil { return fmt.Errorf("open index failed: %w", err) @@ -151,31 +196,68 @@ func (a *DBFArea) OrderListClear() error { return nil } -// OrderListFocus sets the active index by tag name or number. +// OrderListFocus sets the active index by tag name, number, or file name. +// Harbour: OrdSetFocus(nOrder) or OrdSetFocus("tagName") func (a *DBFArea) OrderListFocus(tagName string) error { a.ensureIndexState() if tagName == "" || tagName == "0" { a.idxState.current = -1 // natural order + a.ClearScope() return nil } - upper := strings.ToUpper(tagName) - for i, name := range a.idxState.tags { - if strings.ToUpper(name) == upper { - a.idxState.current = i + + // Try as numeric order (1-based) + if n, err := parseOrderNum(tagName); err == nil { + if n == 0 { + a.idxState.current = -1 + a.ClearScope() + return nil + } + if n >= 1 && n <= len(a.idxState.indexes) { + a.idxState.current = n - 1 + a.ClearScope() return nil } } - // Try by file name + + upper := strings.ToUpper(tagName) + // Match by tag name + for i, name := range a.idxState.tags { + if strings.ToUpper(name) == upper { + a.idxState.current = i + a.ClearScope() + return nil + } + } + // Match by file name for i, name := range a.idxState.names { base := strings.ToUpper(filepath.Base(name)) - if base == upper || strings.TrimSuffix(base, ".NTX") == upper { + ext := strings.ToUpper(filepath.Ext(name)) + if base == upper || strings.TrimSuffix(base, ext) == upper { a.idxState.current = i + a.ClearScope() return nil } } return fmt.Errorf("index not found: %s", tagName) } +// parseOrderNum tries to parse a string as a positive integer (order number). +func parseOrderNum(s string) (int, error) { + s = strings.TrimSpace(s) + if len(s) == 0 { + return 0, fmt.Errorf("empty") + } + n := 0 + for _, c := range s { + if c < '0' || c > '9' { + return 0, fmt.Errorf("not a number") + } + n = n*10 + int(c-'0') + } + return n, nil +} + // OrderListRebuild rebuilds all indexes. // Harbour: ORDLISTREBUILD / REINDEX — recreates all open indexes from current data. func (a *DBFArea) OrderListRebuild() error { @@ -344,25 +426,83 @@ func (a *DBFArea) Seek(key hbrt.Value, softSeek bool, findLast bool) (bool, erro } // GoTopIndexed positions at the first key in the active index. +// Harbour: if SCOPE is set, positions at the first key >= scopeTop. func (a *DBFArea) GoTopIndexed() error { if a.idxState == nil || a.idxState.current < 0 { return a.GoTop() } idx := a.idxState.indexes[a.idxState.current] + + if a.idxState.scopeTop != nil { + // Seek to scope top boundary + recNo, _ := idx.Seek(a.idxState.scopeTop) + if recNo == 0 || idx.IsEOF() { + rc, _ := a.RecCount() + a.FEof = true + return a.GoTo(rc + 1) + } + // Check if within bottom scope + if a.idxState.scopeBottom != nil { + if bytes.Compare(idx.CurKey(), a.idxState.scopeBottom) > 0 { + rc, _ := a.RecCount() + a.FEof = true + return a.GoTo(rc + 1) + } + } + return a.GoTo(idx.CurRecNo()) + } + idx.GoTop() if idx.IsEOF() { rc, _ := a.RecCount() + a.FEof = true return a.GoTo(rc + 1) } return a.GoTo(idx.CurRecNo()) } // GoBottomIndexed positions at the last key in the active index. +// Harbour: if SCOPE is set, positions at the last key <= scopeBottom. func (a *DBFArea) GoBottomIndexed() error { if a.idxState == nil || a.idxState.current < 0 { return a.GoBottom() } idx := a.idxState.indexes[a.idxState.current] + + if a.idxState.scopeBottom != nil { + // Seek to scope bottom boundary + _, exact := idx.Seek(a.idxState.scopeBottom) + if idx.IsEOF() { + // All keys less than bottom scope — go to physical bottom + idx.GoBottom() + } else if !exact { + // Positioned past bottom — go back one + idx.SkipPrev() + } else { + // Exact match — skip forward to last matching key, then position there + for { + idx.SkipNext() + if idx.IsEOF() || bytes.Compare(idx.CurKey(), a.idxState.scopeBottom) > 0 { + idx.SkipPrev() + break + } + } + } + if idx.IsBOF() || idx.IsEOF() { + a.FBof = true + return a.GoTo(1) + } + // Verify within top scope + if a.idxState.scopeTop != nil { + if bytes.Compare(idx.CurKey(), a.idxState.scopeTop) < 0 { + a.FEof = true + rc, _ := a.RecCount() + return a.GoTo(rc + 1) + } + } + return a.GoTo(idx.CurRecNo()) + } + idx.GoBottom() if idx.IsBOF() { return a.GoTo(1) @@ -371,11 +511,13 @@ func (a *DBFArea) GoBottomIndexed() error { } // SkipIndexed skips using the active index order. +// Harbour: respects SCOPE boundaries — stops at scope edges. func (a *DBFArea) SkipIndexed(count int64) error { if a.idxState == nil || a.idxState.current < 0 { return a.Skip(count) } idx := a.idxState.indexes[a.idxState.current] + hasScope := a.idxState.scopeTop != nil || a.idxState.scopeBottom != nil if count > 0 { for i := int64(0); i < count; i++ { @@ -386,18 +528,188 @@ func (a *DBFArea) SkipIndexed(count int64) error { a.FEof = true return nil } + // Check bottom scope + if hasScope && a.idxState.scopeBottom != nil { + if bytes.Compare(idx.CurKey(), a.idxState.scopeBottom) > 0 { + rc, _ := a.RecCount() + a.GoTo(rc + 1) + a.FEof = true + return nil + } + } } } else if count < 0 { for i := int64(0); i > count; i-- { idx.SkipPrev() if idx.IsBOF() { + a.FBof = true + // Stay at first record in scope + if a.idxState.scopeTop != nil { + idx.Seek(a.idxState.scopeTop) + } else { + idx.GoTop() + } + if !idx.IsEOF() { + return a.GoTo(idx.CurRecNo()) + } return a.GoTo(1) } + // Check top scope + if hasScope && a.idxState.scopeTop != nil { + if bytes.Compare(idx.CurKey(), a.idxState.scopeTop) < 0 { + a.FBof = true + idx.Seek(a.idxState.scopeTop) + if !idx.IsEOF() { + return a.GoTo(idx.CurRecNo()) + } + return a.GoTo(1) + } + } } } return a.GoTo(idx.CurRecNo()) } +// --- Scope support (ORDSCOPE) --- + +// SetScope sets top and/or bottom scope boundaries for the active index. +// Harbour: OrdScope(TOPSCOPE, val) / OrdScope(BOTTOMSCOPE, val) +// Pass zero-value hbrt.Value{} (not MakeNil) to skip setting that boundary. +func (a *DBFArea) SetScope(top, bottom hbrt.Value) error { + a.ensureIndexState() + if a.idxState.current < 0 { + return fmt.Errorf("no active index") + } + idx := a.idxState.indexes[a.idxState.current] + keyLen := idx.KeyLen() + + if !top.IsNil() && top.Type() != 0 { + a.idxState.scopeTop = scopeKeyFromValue(top, keyLen) + } + if !bottom.IsNil() && bottom.Type() != 0 { + a.idxState.scopeBottom = scopeKeyFromValue(bottom, keyLen) + } + return nil +} + +// SetScopeTop sets only the top scope. +func (a *DBFArea) SetScopeTop(val hbrt.Value) { + a.ensureIndexState() + if a.idxState.current < 0 { + return + } + keyLen := a.idxState.indexes[a.idxState.current].KeyLen() + a.idxState.scopeTop = scopeKeyFromValue(val, keyLen) +} + +// SetScopeBottom sets only the bottom scope. +func (a *DBFArea) SetScopeBottom(val hbrt.Value) { + a.ensureIndexState() + if a.idxState.current < 0 { + return + } + keyLen := a.idxState.indexes[a.idxState.current].KeyLen() + a.idxState.scopeBottom = scopeKeyFromValue(val, keyLen) +} + +// ClearScope removes all scope boundaries. +func (a *DBFArea) ClearScope() error { + if a.idxState != nil { + a.idxState.scopeTop = nil + a.idxState.scopeBottom = nil + } + return nil +} + +// ClearScopeTop removes only the top scope boundary. +func (a *DBFArea) ClearScopeTop() { + if a.idxState != nil { + a.idxState.scopeTop = nil + } +} + +// ClearScopeBottom removes only the bottom scope boundary. +func (a *DBFArea) ClearScopeBottom() { + if a.idxState != nil { + a.idxState.scopeBottom = nil + } +} + +// GetScopeTop returns the current top scope key (nil if none). +func (a *DBFArea) GetScopeTop() []byte { + if a.idxState != nil { + return a.idxState.scopeTop + } + return nil +} + +// GetScopeBottom returns the current bottom scope key (nil if none). +func (a *DBFArea) GetScopeBottom() []byte { + if a.idxState != nil { + return a.idxState.scopeBottom + } + return nil +} + +// scopeKeyFromValue converts a Harbour Value to a scope key byte slice. +func scopeKeyFromValue(v hbrt.Value, keyLen int) []byte { + var key []byte + if v.IsString() { + key = []byte(v.AsString()) + } else if v.IsNumeric() { + key = []byte(fmt.Sprintf("%*d", keyLen, v.AsNumInt())) + } else { + key = []byte(v.AsString()) + } + // Pad to keyLen + if len(key) < keyLen { + padded := make([]byte, keyLen) + copy(padded, key) + for i := len(key); i < keyLen; i++ { + padded[i] = ' ' + } + return padded + } + if len(key) > keyLen { + return key[:keyLen] + } + return key +} + +// --- Index info accessors --- + +// IndexCount returns the number of open indexes. +func (a *DBFArea) IndexCount() int { + if a.idxState == nil { + return 0 + } + return len(a.idxState.indexes) +} + +// CurrentOrder returns the 1-based current order number (0 = natural). +func (a *DBFArea) CurrentOrder() int { + if a.idxState == nil || a.idxState.current < 0 { + return 0 + } + return a.idxState.current + 1 +} + +// OrderName returns the tag name for order n (1-based). +func (a *DBFArea) OrderName(n int) string { + if a.idxState == nil || n < 1 || n > len(a.idxState.tags) { + return "" + } + return a.idxState.tags[n-1] +} + +// OrderKeyExpr returns the key expression for order n (1-based). +func (a *DBFArea) OrderKeyExpr(n int) string { + if a.idxState == nil || n < 1 || n > len(a.idxState.keyExprs) { + return "" + } + return a.idxState.keyExprs[n-1] +} + // evalKeyExpr evaluates an index key expression for a given record. // Supports: field names, UPPER(), LOWER(), LTRIM(), RTRIM(), ALLTRIM(), // STR(), DTOS(), SUBSTR(), LEFT(), RIGHT(), PADL(), PADR(), diff --git a/hbrt/value.go b/hbrt/value.go index 2b4d6ea..5e3563b 100644 --- a/hbrt/value.go +++ b/hbrt/value.go @@ -22,9 +22,13 @@ package hbrt import ( "fmt" "math" + "strconv" "unsafe" ) +// NtoS converts int64 to string. Used by generated code for SET ORDER TO. +func NtoS(n int64) string { return strconv.FormatInt(n, 10) } + // Value is the fundamental value type in Five (24 bytes). // Scalar types use scalar+info fields (ptr is nil). // Pointer types use ptr field (GC-traced) + info for metadata. diff --git a/hbrtl/indexrtl.go b/hbrtl/indexrtl.go index 8a3a42c..6f91cf7 100644 --- a/hbrtl/indexrtl.go +++ b/hbrtl/indexrtl.go @@ -2,19 +2,30 @@ // All rights reserved. // Index and database introspection RTL functions. +// Harbour: INDEXORD, INDEXKEY, ORDSETFOCUS, ORDCOUNT, ORDNAME, ORDKEY, +// ORDFOR, ORDSCOPE, DBORDERINFO, DBINFO, DBCREATE, RDDSETDEFAULT package hbrtl import ( "five/hbrt" "five/hbrdd" + "five/hbrdd/dbf" ) -// INDEXORD() → nCurrentOrder +// INDEXORD() → nCurrentOrder (1-based, 0 = natural) func IndexOrd(t *hbrt.Thread) { t.Frame(0, 0) defer t.EndProc() - // Simplified: return 0 (no active order) + wam := getWA(t) + if wam != nil { + if area := wam.Current(); area != nil { + if da, ok := area.(*dbf.DBFArea); ok { + t.RetInt(int64(da.CurrentOrder())) + return + } + } + } t.RetInt(0) } @@ -23,10 +34,23 @@ func IndexKey(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() + wam := getWA(t) + if wam != nil { + if area := wam.Current(); area != nil { + if da, ok := area.(*dbf.DBFArea); ok { + n := da.CurrentOrder() + if nParams >= 1 && !t.Local(1).IsNil() { + n = t.Local(1).AsInt() + } + t.RetString(da.OrderKeyExpr(n)) + return + } + } + } t.RetString("") } -// ORDSETFOCUS([nOrder|cTag]) → nOldOrder +// ORDSETFOCUS([nOrder|cTag [, cBagName]]) → nOldOrder func OrdSetFocus(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) @@ -41,46 +65,167 @@ func OrdSetFocus(t *hbrt.Thread) { t.RetInt(0) return } + da, isDa := area.(*dbf.DBFArea) + oldOrd := 0 + if isDa { + oldOrd = da.CurrentOrder() + } if nParams >= 1 && !t.Local(1).IsNil() { if idx, ok := area.(hbrdd.Indexer); ok { - tag := t.Local(1).AsString() - idx.OrderListFocus(tag) + v := t.Local(1) + if v.IsNumeric() { + // SET ORDER TO n + idx.OrderListFocus(v.AsString()) + } else { + idx.OrderListFocus(v.AsString()) + } + } + } + t.RetInt(int64(oldOrd)) +} + +// ORDCOUNT([cBagName]) → nOrders +func OrdCount(t *hbrt.Thread) { + nParams := t.ParamCount() + t.Frame(nParams, 0) + defer t.EndProc() + wam := getWA(t) + if wam != nil { + if area := wam.Current(); area != nil { + if da, ok := area.(*dbf.DBFArea); ok { + t.RetInt(int64(da.IndexCount())) + return + } } } t.RetInt(0) } -// ORDCOUNT() → nOrders -func OrdCount(t *hbrt.Thread) { - t.Frame(0, 0) - defer t.EndProc() - t.RetInt(0) -} - -// ORDNAME([nOrder]) → cTagName +// ORDNAME([nOrder [, cBagName]]) → cTagName func OrdName(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() + wam := getWA(t) + if wam != nil { + if area := wam.Current(); area != nil { + if da, ok := area.(*dbf.DBFArea); ok { + n := da.CurrentOrder() + if nParams >= 1 && !t.Local(1).IsNil() { + n = t.Local(1).AsInt() + } + t.RetString(da.OrderName(n)) + return + } + } + } t.RetString("") } -// ORDKEY([nOrder]) → cKeyExpression +// ORDKEY([nOrder [, cBagName]]) → cKeyExpression func OrdKey(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() + wam := getWA(t) + if wam != nil { + if area := wam.Current(); area != nil { + if da, ok := area.(*dbf.DBFArea); ok { + n := da.CurrentOrder() + if nParams >= 1 && !t.Local(1).IsNil() { + n = t.Local(1).AsInt() + } + t.RetString(da.OrderKeyExpr(n)) + return + } + } + } t.RetString("") } -// ORDFOR([nOrder]) → cForExpression +// ORDFOR([nOrder [, cBagName]]) → cForExpression func OrdFor(t *hbrt.Thread) { nParams := t.ParamCount() t.Frame(nParams, 0) defer t.EndProc() + // TODO: return FOR expression from index t.RetString("") } +// ORDSCOPE(nScope [, xValue]) → xOldValue +// nScope: 0 = TOPSCOPE, 1 = BOTTOMSCOPE +// If xValue omitted, returns current scope. If xValue given, sets scope and returns old. +// Harbour: TOPSCOPE = 0, BOTTOMSCOPE = 1 +func OrdScope(t *hbrt.Thread) { + nParams := t.ParamCount() + t.Frame(nParams, 0) + defer t.EndProc() + + wam := getWA(t) + if wam == nil { + t.RetNil() + return + } + area := wam.Current() + if area == nil { + t.RetNil() + return + } + da, ok := area.(*dbf.DBFArea) + if !ok { + t.RetNil() + return + } + + nScope := 0 + if nParams >= 1 { + nScope = t.Local(1).AsInt() + } + + // Get old scope value + var oldScope []byte + if nScope == 0 { + oldScope = da.GetScopeTop() + } else { + oldScope = da.GetScopeBottom() + } + + if oldScope != nil { + t.PushString(string(oldScope)) + } else { + t.PushNil() + } + + // Set new scope if value provided + if nParams >= 2 { + val := t.Local(2) + if val.IsNil() { + if nScope == 0 { + da.ClearScopeTop() + } else { + da.ClearScopeBottom() + } + } else { + if nScope == 0 { + da.SetScopeTop(val) + } else { + da.SetScopeBottom(val) + } + } + } + + t.RetValue() +} + +// DBORDERINFO(nInfoType [, cBagName [, nOrder [, xNewSetting]]]) → xInfo +func DbOrderInfo(t *hbrt.Thread) { + nParams := t.ParamCount() + t.Frame(nParams, 0) + defer t.EndProc() + // TODO: implement full DBORDERINFO + t.RetNil() +} + // DBINFO(nInfoType [, xNewSetting]) → xInfo func DbInfo(t *hbrt.Thread) { nParams := t.ParamCount() diff --git a/hbrtl/register.go b/hbrtl/register.go index fba1f17..b3093c0 100644 --- a/hbrtl/register.go +++ b/hbrtl/register.go @@ -411,6 +411,8 @@ func RegisterRTL(vm *hbrt.VM) { hbrt.Sym("ORDFOR", hbrt.FsPublic, OrdFor), hbrt.Sym("DBINFO", hbrt.FsPublic, DbInfo), hbrt.Sym("ORDINFO", hbrt.FsPublic, OrdInfo), + hbrt.Sym("ORDSCOPE", hbrt.FsPublic, OrdScope), + hbrt.Sym("DBORDERINFO", hbrt.FsPublic, DbOrderInfo), hbrt.Sym("RDDSETDEFAULT", hbrt.FsPublic, RddSetDefault), hbrt.Sym("DBCREATE", hbrt.FsPublic, DbCreate),