Built-in methods on basic types, enabling fluent chaining:
cStr:Trim():Upper():Left(5) → "HELLO"
aArr:Sort():Join(",") → "1,2,3"
String (20 methods):
Upper, Lower, Trim, LTrim, RTrim, Left, Right, Substr,
Len, Replace, Split, Contains, Starts, Ends, Reverse,
Replicate, Copy, At, Empty, Val
Array (14 methods):
Len, Push, Pop, Sort, Find, Map, Filter, Each,
Join, Copy, Empty, First, Last, Slice
Numeric (6 methods):
Str, Round, Int, Abs, Sqrt, Copy
Hash (7 methods):
Keys, Values, Has, Len, Copy, Delete, Empty
Any type (5 methods):
Copy, Type, IsNil, ToStr, ClassName
Integration: Thread.Send() checks SendBuiltin() before class dispatch.
Tests: 28 tests ALL PASS including chaining test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
209 lines
6.7 KiB
Go
209 lines
6.7 KiB
Go
package hbrt
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func sendMethod(t *testing.T, self Value, method string, args ...Value) Value {
|
|
t.Helper()
|
|
vm := NewVM()
|
|
th := vm.NewThread()
|
|
th.Frame(0, 0)
|
|
result, ok := SendBuiltin(th, self, method, args)
|
|
if !ok {
|
|
t.Fatalf("method %s not found", method)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// === String methods ===
|
|
|
|
func TestVM_StrUpper(t *testing.T) {
|
|
r := sendMethod(t, MakeString("hello"), "Upper")
|
|
if r.AsString() != "HELLO" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrLower(t *testing.T) {
|
|
r := sendMethod(t, MakeString("HELLO"), "Lower")
|
|
if r.AsString() != "hello" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrTrim(t *testing.T) {
|
|
r := sendMethod(t, MakeString(" hello "), "Trim")
|
|
if r.AsString() != "hello" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrLeft(t *testing.T) {
|
|
r := sendMethod(t, MakeString("Hello World"), "Left", MakeInt(5))
|
|
if r.AsString() != "Hello" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrRight(t *testing.T) {
|
|
r := sendMethod(t, MakeString("Hello World"), "Right", MakeInt(5))
|
|
if r.AsString() != "World" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrReplace(t *testing.T) {
|
|
r := sendMethod(t, MakeString("Hello World"), "Replace", MakeString("World"), MakeString("Five"))
|
|
if r.AsString() != "Hello Five" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrSplit(t *testing.T) {
|
|
r := sendMethod(t, MakeString("a,b,c"), "Split", MakeString(","))
|
|
if !r.IsArray() { t.Fatal("not array") }
|
|
if len(r.AsArray().Items) != 3 { t.Errorf("len=%d", len(r.AsArray().Items)) }
|
|
if r.AsArray().Items[0].AsString() != "a" { t.Errorf("[0]=%q", r.AsArray().Items[0].AsString()) }
|
|
}
|
|
|
|
func TestVM_StrLen(t *testing.T) {
|
|
r := sendMethod(t, MakeString("Hello"), "Len")
|
|
if r.AsInt() != 5 { t.Errorf("got %d", r.AsInt()) }
|
|
}
|
|
|
|
func TestVM_StrContains(t *testing.T) {
|
|
r := sendMethod(t, MakeString("Hello World"), "Contains", MakeString("World"))
|
|
if !r.AsBool() { t.Error("should be true") }
|
|
}
|
|
|
|
func TestVM_StrReverse(t *testing.T) {
|
|
r := sendMethod(t, MakeString("Hello"), "Reverse")
|
|
if r.AsString() != "olleH" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrCopy(t *testing.T) {
|
|
r := sendMethod(t, MakeString("test"), "Copy")
|
|
if r.AsString() != "test" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_StrEmpty(t *testing.T) {
|
|
if !sendMethod(t, MakeString(""), "Empty").AsBool() { t.Error("empty string") }
|
|
if !sendMethod(t, MakeString(" "), "Empty").AsBool() { t.Error("spaces") }
|
|
if sendMethod(t, MakeString("x"), "Empty").AsBool() { t.Error("non-empty") }
|
|
}
|
|
|
|
// === Array methods ===
|
|
|
|
func TestVM_ArrLen(t *testing.T) {
|
|
arr := MakeArrayFrom([]Value{MakeInt(1), MakeInt(2), MakeInt(3)})
|
|
r := sendMethod(t, arr, "Len")
|
|
if r.AsInt() != 3 { t.Errorf("got %d", r.AsInt()) }
|
|
}
|
|
|
|
func TestVM_ArrSort(t *testing.T) {
|
|
arr := MakeArrayFrom([]Value{MakeInt(3), MakeInt(1), MakeInt(2)})
|
|
r := sendMethod(t, arr, "Sort")
|
|
items := r.AsArray().Items
|
|
if items[0].AsInt() != 1 || items[1].AsInt() != 2 || items[2].AsInt() != 3 {
|
|
t.Errorf("got %v %v %v", items[0].AsInt(), items[1].AsInt(), items[2].AsInt())
|
|
}
|
|
}
|
|
|
|
func TestVM_ArrFind(t *testing.T) {
|
|
arr := MakeArrayFrom([]Value{MakeString("a"), MakeString("b"), MakeString("c")})
|
|
r := sendMethod(t, arr, "Find", MakeString("b"))
|
|
if r.AsInt() != 2 { t.Errorf("got %d", r.AsInt()) }
|
|
r = sendMethod(t, arr, "Find", MakeString("z"))
|
|
if r.AsInt() != 0 { t.Errorf("not found: got %d", r.AsInt()) }
|
|
}
|
|
|
|
func TestVM_ArrJoin(t *testing.T) {
|
|
arr := MakeArrayFrom([]Value{MakeString("a"), MakeString("b"), MakeString("c")})
|
|
r := sendMethod(t, arr, "Join", MakeString("-"))
|
|
if r.AsString() != "a-b-c" { t.Errorf("got %q", r.AsString()) }
|
|
}
|
|
|
|
func TestVM_ArrCopy(t *testing.T) {
|
|
arr := MakeArrayFrom([]Value{MakeInt(1), MakeInt(2)})
|
|
cp := sendMethod(t, arr, "Copy")
|
|
// Modify original — copy should not change
|
|
arr.AsArray().Items[0] = MakeInt(99)
|
|
if cp.AsArray().Items[0].AsInt() != 1 { t.Error("not a deep copy") }
|
|
}
|
|
|
|
func TestVM_ArrFirstLast(t *testing.T) {
|
|
arr := MakeArrayFrom([]Value{MakeInt(10), MakeInt(20), MakeInt(30)})
|
|
if sendMethod(t, arr, "First").AsInt() != 10 { t.Error("first") }
|
|
if sendMethod(t, arr, "Last").AsInt() != 30 { t.Error("last") }
|
|
}
|
|
|
|
func TestVM_ArrEmpty(t *testing.T) {
|
|
if !sendMethod(t, MakeArrayFrom(nil), "Empty").AsBool() { t.Error("empty") }
|
|
if sendMethod(t, MakeArrayFrom([]Value{MakeInt(1)}), "Empty").AsBool() { t.Error("non-empty") }
|
|
}
|
|
|
|
// === Numeric methods ===
|
|
|
|
func TestVM_NumRound(t *testing.T) {
|
|
r := sendMethod(t, MakeDouble(3.14159, 0, 0), "Round", MakeInt(2))
|
|
diff := r.AsDouble() - 3.14
|
|
if diff > 0.001 || diff < -0.001 { t.Errorf("got %f", r.AsDouble()) }
|
|
}
|
|
|
|
func TestVM_NumAbs(t *testing.T) {
|
|
r := sendMethod(t, MakeDouble(-42.5, 0, 0), "Abs")
|
|
if r.AsDouble() != 42.5 { t.Errorf("got %f", r.AsDouble()) }
|
|
}
|
|
|
|
func TestVM_NumInt(t *testing.T) {
|
|
r := sendMethod(t, MakeDouble(3.7, 0, 0), "Int")
|
|
if r.AsInt() != 3 { t.Errorf("got %d", r.AsInt()) }
|
|
}
|
|
|
|
// === Hash methods ===
|
|
|
|
func TestVM_HashKeys(t *testing.T) {
|
|
h := MakeHash()
|
|
hh := h.AsHash()
|
|
hh.Keys = append(hh.Keys, MakeString("name"), MakeString("age"))
|
|
hh.Values = append(hh.Values, MakeString("Charles"), MakeInt(30))
|
|
|
|
r := sendMethod(t, h, "Keys")
|
|
if !r.IsArray() || len(r.AsArray().Items) != 2 { t.Error("keys") }
|
|
}
|
|
|
|
func TestVM_HashHas(t *testing.T) {
|
|
h := MakeHash()
|
|
hh := h.AsHash()
|
|
hh.Keys = append(hh.Keys, MakeString("name"))
|
|
hh.Values = append(hh.Values, MakeString("Charles"))
|
|
|
|
if !sendMethod(t, h, "Has", MakeString("name")).AsBool() { t.Error("has name") }
|
|
if sendMethod(t, h, "Has", MakeString("age")).AsBool() { t.Error("no age") }
|
|
}
|
|
|
|
func TestVM_HashCopy(t *testing.T) {
|
|
h := MakeHash()
|
|
hh := h.AsHash()
|
|
hh.Keys = append(hh.Keys, MakeString("k"))
|
|
hh.Values = append(hh.Values, MakeInt(1))
|
|
|
|
cp := sendMethod(t, h, "Copy")
|
|
hh.Values[0] = MakeInt(99) // modify original
|
|
if cp.AsHash().Values[0].AsInt() != 1 { t.Error("not deep copy") }
|
|
}
|
|
|
|
// === Any type methods ===
|
|
|
|
func TestVM_AnyType(t *testing.T) {
|
|
if sendMethod(t, MakeString("x"), "Type").AsString() != "C" { t.Error("C") }
|
|
if sendMethod(t, MakeInt(1), "Type").AsString() != "N" { t.Error("N") }
|
|
if sendMethod(t, MakeBool(true), "Type").AsString() != "L" { t.Error("L") }
|
|
if sendMethod(t, MakeNil(), "Type").AsString() != "U" { t.Error("U") }
|
|
}
|
|
|
|
func TestVM_AnyToStr(t *testing.T) {
|
|
if sendMethod(t, MakeInt(42), "ToStr").AsString() != "42" { t.Error("42") }
|
|
if sendMethod(t, MakeBool(true), "ToStr").AsString() != ".T." { t.Error(".T.") }
|
|
}
|
|
|
|
// === Chaining ===
|
|
|
|
func TestVM_StringChaining(t *testing.T) {
|
|
// " Hello World " :Trim() :Upper() :Left(5) → "HELLO"
|
|
s := MakeString(" Hello World ")
|
|
r, _ := SendBuiltin(nil, s, "Trim", nil)
|
|
r, _ = SendBuiltin(nil, r, "Upper", nil)
|
|
r, _ = SendBuiltin(nil, r, "Left", []Value{MakeInt(5)})
|
|
if r.AsString() != "HELLO" { t.Errorf("chain: got %q", r.AsString()) }
|
|
}
|