feat: CDX support + ORDSCOPE + cross-read Harbour compatibility

CDX Integration:
- IndexEngine interface: common for NTX Index and CDX Tag
- OrderListAdd: auto-detects .cdx/.ntx extension, opens CDX tags
- decodeCompoundLeaf: proper bit-packed tag directory decoding
  (was stub falling through to scanCompoundLeaves with wrong names)
- CDX Tag: added KeyLen(), KeyExpr(), ForExpr(), IsDescending(), Close()
- CDX compound recNo = direct byte offset (not page number)

ORDSCOPE:
- SetScope/ClearScope/SetScopeTop/SetScopeBottom on DBFArea
- GoTopIndexed: seeks to scopeTop, validates within scopeBottom
- GoBottomIndexed: seeks to scopeBottom boundary
- SkipIndexed: stops at scope boundaries (top and bottom)
- OrdScope RTL function registered (nScope: 0=TOP, 1=BOTTOM)
- scopeKeyFromValue: converts Value to padded key bytes

Index Order Management:
- OrderListFocus: handles numeric order ("2" → order 2)
- SET ORDER TO n: gengo emits hbrt.NtoS for int-to-string conversion
- IndexOrd/OrdCount/OrdName/OrdKey: real implementations (were stubs)
- OrderCount/CurrentOrder/OrderName/OrderKeyExpr accessors on DBFArea
- ClearScope on order switch (prevents stale scope)

Cross-read test: Harbour-created CDX → Five reads, 20/20 items match:
  NAME/CITY/ID seek, ORDSCOPE count, GoTop/GoBottom all identical

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 12:21:26 +09:00
parent 441d6c184f
commit 7e2a159b88
7 changed files with 613 additions and 30 deletions

View File

@@ -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("}")
}

View File

@@ -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

View File

@@ -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 {

View File

@@ -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), ".") {
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
}
// 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
}
}
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
}
}
// Try by file name
// 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(),

View File

@@ -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.

View File

@@ -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(0)
}
t.RetInt(int64(oldOrd))
}
// ORDCOUNT() → nOrders
// ORDCOUNT([cBagName]) → nOrders
func OrdCount(t *hbrt.Thread) {
t.Frame(0, 0)
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)
}
// 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()

View File

@@ -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),