diff --git a/Makefile b/Makefile index db4f4548..279747aa 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ STDLIB := $(STDLIB_ORIGINALS:scripts/rewrite-core/originals/%=%) STDLIB_ORIGINALS := $(addprefix scripts/rewrite-core/originals/,$(STDLIB)) STDLIB_TARGETS := $(addprefix pkg/stdlib/glojure/,$(STDLIB:.clj=.glj)) -TEST_FILES := $(shell find ./test -name '*.glj') +TEST_FILES := $(shell find ./test -name '*.glj' | sort) TEST_TARGETS := $(addsuffix .test,$(TEST_FILES)) GOPLATFORMS := darwin_arm64 darwin_amd64 linux_arm64 linux_amd64 windows_amd64 windows_arm js_wasm @@ -60,3 +60,10 @@ $(TEST_TARGETS): gocmd .PHONY: test test: vet $(TEST_TARGETS) + +.PHONY: format +format: + @if go fmt ./... | grep -q .; then \ + echo "Files were formatted. Please commit the changes."; \ + exit 1; \ + fi diff --git a/internal/deps/pull.go b/internal/deps/pull.go index 594830d3..3072c424 100644 --- a/internal/deps/pull.go +++ b/internal/deps/pull.go @@ -1,3 +1 @@ package deps - - diff --git a/internal/persistent/vector/vector.go b/internal/persistent/vector/vector.go index d460496e..31ead381 100644 --- a/internal/persistent/vector/vector.go +++ b/internal/persistent/vector/vector.go @@ -46,10 +46,10 @@ type Vector interface { // Iterator is an iterator over vector elements. It can be used like this: // -// for it := v.Iterator(); it.HasElem(); it.Next() { -// elem := it.Elem() -// // do something with elem... -// } +// for it := v.Iterator(); it.HasElem(); it.Next() { +// elem := it.Elem() +// // do something with elem... +// } type Iterator interface { // Elem returns the element at the current position. Elem() interface{} diff --git a/pkg/gen/gljimports/gljimports_darwin_amd64.go b/pkg/gen/gljimports/gljimports_darwin_amd64.go index a2aec26b..9e7c3739 100644 --- a/pkg/gen/gljimports/gljimports_darwin_amd64.go +++ b/pkg/gen/gljimports/gljimports_darwin_amd64.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/gen/gljimports/gljimports_darwin_arm64.go b/pkg/gen/gljimports/gljimports_darwin_arm64.go index ab06fe1e..f4133476 100644 --- a/pkg/gen/gljimports/gljimports_darwin_arm64.go +++ b/pkg/gen/gljimports/gljimports_darwin_arm64.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/gen/gljimports/gljimports_js_wasm.go b/pkg/gen/gljimports/gljimports_js_wasm.go index e99d3d9b..adfec5ae 100644 --- a/pkg/gen/gljimports/gljimports_js_wasm.go +++ b/pkg/gen/gljimports/gljimports_js_wasm.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/gen/gljimports/gljimports_linux_amd64.go b/pkg/gen/gljimports/gljimports_linux_amd64.go index e5310d57..9d7331e7 100644 --- a/pkg/gen/gljimports/gljimports_linux_amd64.go +++ b/pkg/gen/gljimports/gljimports_linux_amd64.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/gen/gljimports/gljimports_linux_arm64.go b/pkg/gen/gljimports/gljimports_linux_arm64.go index 852fcb1c..7e427803 100644 --- a/pkg/gen/gljimports/gljimports_linux_arm64.go +++ b/pkg/gen/gljimports/gljimports_linux_arm64.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/gen/gljimports/gljimports_windows_amd64.go b/pkg/gen/gljimports/gljimports_windows_amd64.go index 8376188e..6de5d4b4 100644 --- a/pkg/gen/gljimports/gljimports_windows_amd64.go +++ b/pkg/gen/gljimports/gljimports_windows_amd64.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/gen/gljimports/gljimports_windows_arm.go b/pkg/gen/gljimports/gljimports_windows_arm.go index 100d9ee4..ff2506e9 100644 --- a/pkg/gen/gljimports/gljimports_windows_arm.go +++ b/pkg/gen/gljimports/gljimports_windows_arm.go @@ -3466,6 +3466,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*ChunkedCons", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.ChunkedCons)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.CloneThreadBindingFrame", github_com_glojurelang_glojure_pkg_lang.CloneThreadBindingFrame) + _register("github.com/glojurelang/glojure/pkg/lang.Compare", github_com_glojurelang_glojure_pkg_lang.Compare) _register("github.com/glojurelang/glojure/pkg/lang.Comparer", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Comparer)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.ConcatStrings", github_com_glojurelang_glojure_pkg_lang.ConcatStrings) _register("github.com/glojurelang/glojure/pkg/lang.Conj", github_com_glojurelang_glojure_pkg_lang.Conj) @@ -3888,6 +3889,7 @@ func RegisterImports(_register func(string, interface{})) { _register("github.com/glojurelang/glojure/pkg/lang.SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*SliceSeq", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.SliceSeq)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.SliceSet", github_com_glojurelang_glojure_pkg_lang.SliceSet) + _register("github.com/glojurelang/glojure/pkg/lang.SortSlice", github_com_glojurelang_glojure_pkg_lang.SortSlice) _register("github.com/glojurelang/glojure/pkg/lang.StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil)).Elem()) _register("github.com/glojurelang/glojure/pkg/lang.*StackFrame", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.StackFrame)(nil))) _register("github.com/glojurelang/glojure/pkg/lang.Stacker", reflect.TypeOf((*github_com_glojurelang_glojure_pkg_lang.Stacker)(nil)).Elem()) diff --git a/pkg/lang/apersistentvector.go b/pkg/lang/apersistentvector.go index 47b90b9e..20b05f33 100644 --- a/pkg/lang/apersistentvector.go +++ b/pkg/lang/apersistentvector.go @@ -13,6 +13,7 @@ type ( IPersistentVector IHashEq Reversible + Comparer } apvSeq struct { diff --git a/pkg/lang/keyword.go b/pkg/lang/keyword.go index 1d9a10ad..f678c305 100644 --- a/pkg/lang/keyword.go +++ b/pkg/lang/keyword.go @@ -98,3 +98,10 @@ func (k Keyword) ApplyTo(args ISeq) interface{} { func (k Keyword) Hash() uint32 { return k.hash } + +func (k Keyword) Compare(other any) int { + if otherKw, ok := other.(Keyword); ok { + return strings.Compare(k.String(), otherKw.String()) + } + panic(NewIllegalArgumentError(fmt.Sprintf("Cannot compare Keyword with %T", other))) +} diff --git a/pkg/lang/mapentry.go b/pkg/lang/mapentry.go index 80f4bb39..922ebfb7 100644 --- a/pkg/lang/mapentry.go +++ b/pkg/lang/mapentry.go @@ -1,5 +1,7 @@ package lang +import "fmt" + // MapEntry represents a key-value pair in a map. type MapEntry struct { hasheq uint32 @@ -118,3 +120,29 @@ func (me *MapEntry) ValAt(key any) any { func (me *MapEntry) ValAtDefault(key, notFound any) any { return apersistentVectorValAtDefault(me, key, notFound) } + +func (me *MapEntry) Compare(other any) int { + otherVec, ok := other.(IPersistentVector) + if !ok { + panic(NewIllegalArgumentError(fmt.Sprintf("Cannot compare MapEntry with %T", other))) + } + + myCount := me.Count() + otherCount := otherVec.Count() + + // Compare lengths first + if myCount < otherCount { + return -1 + } else if myCount > otherCount { + return 1 + } + + // Compare element by element + for i := 0; i < myCount; i++ { + cmp := Compare(me.Nth(i), otherVec.Nth(i)) + if cmp != 0 { + return cmp + } + } + return 0 +} diff --git a/pkg/lang/numbers.go b/pkg/lang/numbers.go index 35b3e23e..10d121f2 100644 --- a/pkg/lang/numbers.go +++ b/pkg/lang/numbers.go @@ -189,6 +189,16 @@ func (nm *NumberMethods) Equiv(x, y any) bool { return Ops(x).Combine(Ops(y)).Equiv(x, y) } +func (nm *NumberMethods) Compare(x, y any) int { + ops := Ops(x).Combine(Ops(y)) + if ops.LT(x, y) { + return -1 + } else if ops.LT(y, x) { + return 1 + } + return 0 +} + func (nm *NumberMethods) Floats(x any) []float32 { return x.([]float32) } diff --git a/pkg/lang/slices.go b/pkg/lang/slices.go index 469cde58..3f1d4b46 100644 --- a/pkg/lang/slices.go +++ b/pkg/lang/slices.go @@ -11,9 +11,59 @@ func SliceSet(slc any, idx int, val any) { } func ToSlice(x any) []any { + // Handle nil - Clojure returns empty array for nil if IsNil(x) { - return nil + return []any{} } + + // Handle []any - return as-is + if slice, ok := x.([]any); ok { + return slice + } + + // Handle IPersistentVector + if vec, ok := x.(IPersistentVector); ok { + count := vec.Count() + res := make([]any, count) + for i := 0; i < count; i++ { + res[i] = vec.Nth(i) + } + return res + } + + // Handle IPersistentMap - convert to array of MapEntry objects + if m, ok := x.(IPersistentMap); ok { + seq := m.Seq() + res := make([]any, 0, m.Count()) + for seq != nil { + res = append(res, seq.First()) // Each element is a MapEntry + seq = seq.Next() + } + return res + } + + // Handle Set - convert to array of values + if s, ok := x.(*Set); ok { + seq := s.Seq() + res := make([]any, 0, s.Count()) + for seq != nil { + res = append(res, seq.First()) + seq = seq.Next() + } + return res + } + + // Handle string - convert to character array + if s, ok := x.(string); ok { + runes := []rune(s) // Important: use runes for proper Unicode handling + res := make([]any, len(runes)) + for i, ch := range runes { + res[i] = NewChar(ch) // Convert each rune to Char + } + return res + } + + // Handle ISeq if s, ok := x.(ISeq); ok { res := make([]interface{}, 0, Count(x)) for s := Seq(s); s != nil; s = s.Next() { @@ -21,6 +71,8 @@ func ToSlice(x any) []any { } return res } + + // Handle reflection-based slice/array xVal := reflect.ValueOf(x) if xVal.Kind() == reflect.Slice || xVal.Kind() == reflect.Array { res := make([]interface{}, xVal.Len()) @@ -29,5 +81,7 @@ func ToSlice(x any) []any { } return res } - panic(fmt.Errorf("ToSlice not supported on type: %T", x)) + + // Error with Clojure-style message + panic(NewIllegalArgumentError(fmt.Sprintf("Unable to convert: %T to Object[]", x))) } diff --git a/pkg/lang/sort.go b/pkg/lang/sort.go new file mode 100644 index 00000000..e7d59019 --- /dev/null +++ b/pkg/lang/sort.go @@ -0,0 +1,98 @@ +package lang + +import ( + "fmt" + "sort" + "strings" +) + +// SortSlice performs an in-place stable sort on the given array using the provided comparator. +// This matches java.util.Arrays.sort semantics: +// - Stable sort (equal elements maintain their relative order) +// - In-place modification of the array +// - Comparator returns -1 for less than, 0 for equal, 1 for greater than +func SortSlice(slice []any, comp any) { + // comp is a Clojure function that acts as a comparator + compFn, ok := comp.(IFn) + if !ok { + panic(NewIllegalArgumentError("Comparator must be a function")) + } + + // Use sort.SliceStable for stable sorting (maintains relative order of equal elements) + sort.SliceStable(slice, func(i, j int) bool { + // Call the comparator function with the two elements + result := compFn.Invoke(slice[i], slice[j]) + + // Handle both boolean and numeric comparators + // Boolean comparator: returns true if i < j + // Numeric comparator: returns negative if i < j + if boolResult, ok := result.(bool); ok { + return boolResult + } + + // Numeric comparator returns: + // -1 if first arg is less than second + // 0 if args are equal + // 1 if first arg is greater than second + // We return true for "less than" case + resultInt, ok := AsInt(result) + if !ok { + panic(NewIllegalArgumentError(fmt.Sprintf("Comparator must return a boolean or number, got %T", result))) + } + return resultInt < 0 + }) +} + +// Compare implements Clojure's compare function. +// Returns a negative number, zero, or a positive number when x is logically +// 'less than', 'equal to', or 'greater than' y. +// Handles nil values (nil is less than everything except nil). +func Compare(x, y any) int { + // Identity check + if x == y { + return 0 + } + + // Handle nil cases + if IsNil(x) { + if IsNil(y) { + return 0 + } + return -1 + } + if IsNil(y) { + return 1 + } + + // Handle numbers using the Numbers.Compare method + if xNum, xIsNum := AsNumber(x); xIsNum { + return Numbers.Compare(xNum, y) + } + + // Check if x implements Comparer interface + if xComp, ok := x.(Comparer); ok { + return xComp.Compare(y) + } + + // Handle strings (built-in type, doesn't implement Comparer) + if xStr, xOk := x.(string); xOk { + if yStr, yOk := y.(string); yOk { + return strings.Compare(xStr, yStr) + } + } + + // Handle characters + if xChar, xOk := x.(Char); xOk { + if yChar, yOk := y.(Char); yOk { + if xChar < yChar { + return -1 + } else if xChar > yChar { + return 1 + } + return 0 + } + } + + // Default error - cannot compare + panic(NewIllegalArgumentError(fmt.Sprintf("%T cannot be cast to Comparable", x))) +} diff --git a/pkg/lang/subvector.go b/pkg/lang/subvector.go index e48c30e4..b08772bf 100644 --- a/pkg/lang/subvector.go +++ b/pkg/lang/subvector.go @@ -165,3 +165,29 @@ func (v *SubVector) Invoke(args ...any) any { func (v *SubVector) HashEq() uint32 { return apersistentVectorHashEq(&v.hasheq, v) } + +func (v *SubVector) Compare(other any) int { + otherVec, ok := other.(IPersistentVector) + if !ok { + panic(NewIllegalArgumentError(fmt.Sprintf("Cannot compare SubVector with %T", other))) + } + + myCount := v.Count() + otherCount := otherVec.Count() + + // Compare lengths first + if myCount < otherCount { + return -1 + } else if myCount > otherCount { + return 1 + } + + // Compare element by element + for i := 0; i < myCount; i++ { + cmp := Compare(v.Nth(i), otherVec.Nth(i)) + if cmp != 0 { + return cmp + } + } + return 0 +} diff --git a/pkg/lang/symbol.go b/pkg/lang/symbol.go index 009ddbf6..ff882b60 100644 --- a/pkg/lang/symbol.go +++ b/pkg/lang/symbol.go @@ -1,6 +1,7 @@ package lang import ( + "fmt" "strings" ) @@ -48,6 +49,29 @@ func (s *Symbol) Name() string { return s.name } +func (s *Symbol) Compare(other any) int { + otherSym, ok := other.(*Symbol) + if !ok { + panic(NewIllegalArgumentError(fmt.Sprintf("Cannot compare Symbol with %T", other))) + } + + // Compare namespace first + if s.ns != otherSym.ns { + if s.ns == "" && otherSym.ns != "" { + return -1 + } + if s.ns != "" && otherSym.ns == "" { + return 1 + } + if nsComp := strings.Compare(s.ns, otherSym.ns); nsComp != 0 { + return nsComp + } + } + + // Then compare name + return strings.Compare(s.name, otherSym.name) +} + func (s *Symbol) FullName() string { return s.String() } diff --git a/pkg/lang/vector.go b/pkg/lang/vector.go index 26578ee1..a5754341 100644 --- a/pkg/lang/vector.go +++ b/pkg/lang/vector.go @@ -272,6 +272,32 @@ func (v *Vector) AsTransient() ITransientCollection { } } +func (v *Vector) Compare(other any) int { + otherVec, ok := other.(IPersistentVector) + if !ok { + panic(NewIllegalArgumentError(fmt.Sprintf("Cannot compare Vector with %T", other))) + } + + myCount := v.Count() + otherCount := otherVec.Count() + + // Compare lengths first + if myCount < otherCount { + return -1 + } else if myCount > otherCount { + return 1 + } + + // Compare element by element + for i := 0; i < myCount; i++ { + cmp := Compare(v.Nth(i), otherVec.Nth(i)) + if cmp != 0 { + return cmp + } + } + return 0 +} + func toSlice(x any) []any { if x == nil { return nil diff --git a/pkg/stdlib/glojure/core.glj b/pkg/stdlib/glojure/core.glj index 8f907ecd..dbf53637 100644 --- a/pkg/stdlib/glojure/core.glj +++ b/pkg/stdlib/glojure/core.glj @@ -830,7 +830,7 @@ { :inline (fn [x y] `(. glojure.lang.Util compare ~x ~y)) :added "1.0"} - [x y] (. glojure.lang.Util (compare x y))) + [x y] (github.com$glojurelang$glojure$pkg$lang.Compare x y)) (defmacro and "Evaluates exprs one at a time, from left to right. If a form @@ -3090,7 +3090,7 @@ ([^java.util.Comparator comp coll] (if (seq coll) (let [a (to-array coll)] - (. java.util.Arrays (sort a comp)) + (github.com$glojurelang$glojure$pkg$lang.SortSlice a comp) (with-meta (seq a) (meta coll))) ()))) @@ -3106,7 +3106,7 @@ ([keyfn coll] (sort-by keyfn compare coll)) ([keyfn ^java.util.Comparator comp coll] - (sort (fn [x y] (. comp (compare (keyfn x) (keyfn y)))) coll))) + (sort (fn [x y] (comp (keyfn x) (keyfn y))) coll))) (defn dorun "When lazy sequences are produced via functions that have side diff --git a/scripts/rewrite-core/rewrite.clj b/scripts/rewrite-core/rewrite.clj index 506b4241..fd6a3477 100644 --- a/scripts/rewrite-core/rewrite.clj +++ b/scripts/rewrite-core/rewrite.clj @@ -435,6 +435,8 @@ ;; builtin "Equals" (sexpr-replace 'clojure.lang.Util/equiv 'github.com$glojurelang$glojure$pkg$lang.Equiv) (sexpr-replace 'clojure.lang.Util/equals 'github.com$glojurelang$glojure$pkg$lang.Equals) + (sexpr-replace '(. clojure.lang.Util (compare x y)) '(github.com$glojurelang$glojure$pkg$lang.Compare x y)) + (sexpr-replace '(. x (meta)) '(.Meta x)) (sexpr-replace 'clojure.lang.Symbol/intern 'github.com$glojurelang$glojure$pkg$lang.NewSymbol) @@ -938,6 +940,13 @@ (node-replace "(.pattern ^java.util.regex.Pattern p)" "(.String ^regexp.*Regexp p)") + ;; Arrays.sort replacement for Glojure sort function + (sexpr-replace '(. java.util.Arrays (sort a comp)) + '(github.com$glojurelang$glojure$pkg$lang.SortSlice a comp)) + + ;; comparators are simple functions in Glojure + (sexpr-replace '(. comp (compare (keyfn x) (keyfn y))) + '(comp (keyfn x) (keyfn y))) ])) (defn rewrite-core [zloc] diff --git a/test/glojure/test_glojure/sort.glj b/test/glojure/test_glojure/sort.glj new file mode 100644 index 00000000..02e84fdf --- /dev/null +++ b/test/glojure/test_glojure/sort.glj @@ -0,0 +1,291 @@ +;; Tests for sort function +;; Ensures Glojure behavior matches Clojure as closely as possible + +(ns glojure.test-glojure.sort + (:require [glojure.test :refer :all] + [glojure.string :as s])) + +(deftest test-sort-basic + (testing "Basic sort functionality" + ;; Numbers + (is (= '(1 2 3 4 5) (sort [3 1 4 2 5]))) + (is (= '(1 1 3 4 5) (sort [3 1 4 1 5]))) ; duplicates preserved + (is (= '(1.0 2.5 3 4.7) (sort [4.7 1.0 3 2.5]))) ; mixed numeric types + (is (= '(-5 0 1.5 2 3.14 10) (sort [3.14 2 1.5 10 -5 0]))) + + ;; Strings + (is (= '("apple" "banana" "cherry") (sort ["cherry" "apple" "banana"]))) + (is (= '("" "a" "ab" "b") (sort ["b" "a" "" "ab"]))) ; empty string sorts first + + ;; Keywords + (is (= '(:a :b :c) (sort [:c :a :b]))) + (is (= '(:a/x :b/x :c/x) (sort [:c/x :a/x :b/x]))) ; namespaced keywords + + ;; Symbols + (is (= '(a b c) (sort '[c a b]))) + (is (= '(a/x b/x c/x) (sort '[c/x a/x b/x]))) ; namespaced symbols + + ;; Empty collection + (is (= '() (sort []))) + (is (= '() (sort '()))) + (is (= '() (sort nil))) ; nil returns empty seq + + ;; Single element + (is (= '(42) (sort [42]))) + + ;; Already sorted + (is (= '(1 2 3) (sort [1 2 3]))) + + ;; Reverse sorted + (is (= '(1 2 3) (sort [3 2 1]))))) + +(deftest test-sort-with-comparator + (testing "Sort with custom comparator" + ;; Reverse sort + (is (= '(5 4 3 2 1) (sort (fn [a b] (compare b a)) [3 1 4 5 2]))) + (is (= '(5 4 3 2 1) (sort > [3 1 4 5 2]))) ; using > as comparator + + ;; Case-insensitive string sort + (is (= '("apple" "Banana" "cherry") + (sort (fn [a b] (compare (s/lower-case a) (s/lower-case b))) + ["cherry" "apple" "Banana"]))) + + ;; Sort by string length + (is (= '("a" "bb" "ccc" "dddd") + (sort-by count ["ccc" "a" "dddd" "bb"]))) + + ;; Sort maps by a specific key + (let [data [{:name "John" :age 30} + {:name "Jane" :age 25} + {:name "Bob" :age 35}]] + (is (= [{:name "Jane" :age 25} + {:name "John" :age 30} + {:name "Bob" :age 35}] + (sort (fn [a b] (compare (:age a) (:age b))) data)))))) + +(deftest test-sort-nil-handling + (testing "Nil handling in sort" + ;; nil sorts before everything + (is (= '(nil 1 2 3) (sort [3 nil 1 2]))) + (is (= '(nil nil 1 2) (sort [2 nil 1 nil]))) + (is (= '(nil "a" "b") (sort ["b" nil "a"]))) + + ;; With custom comparator that handles nil + (is (= '(3 2 1 nil) + (sort (fn [a b] + (cond + (nil? a) 1 ; nil goes to end + (nil? b) -1 + :else (compare b a))) + [nil 1 2 3]))))) + +(deftest test-sort-different-collection-types + (testing "Sort works on different collection types" + ;; Vector + (is (= '(1 2 3) (sort [3 1 2]))) + + ;; List + (is (= '(1 2 3) (sort '(3 1 2)))) + + ;; Set + (is (= '(1 2 3) (sort #{3 1 2}))) + + ;; Map entries (sorted as vectors) + (let [result (sort {:b 2 :a 1 :c 3})] + (is (= 3 (count result))) + (is (every? vector? result)) + (is (= :a (first (first result))))) + + ;; String (converts to character sequence) + (is (= '(\a \b \c \d) (sort "dcba"))) + + ;; Range + (is (= '(0 1 2 3 4) (sort (reverse (range 5))))))) + +(deftest test-sort-stability + (testing "Sort is stable" + ;; Create items that compare equal but are distinguishable + (let [items [{:id 1 :value 1} + {:id 2 :value 2} + {:id 3 :value 1} + {:id 4 :value 2} + {:id 5 :value 1}] + sorted (sort (fn [a b] (compare (:value a) (:value b))) items)] + ;; Items with same value should maintain relative order + (is (= [{:id 1 :value 1} + {:id 3 :value 1} + {:id 5 :value 1} + {:id 2 :value 2} + {:id 4 :value 2}] + sorted))))) + +(deftest test-sort-metadata-preservation + (testing "Sort preserves metadata" + (let [coll ^{:my-meta true} [3 1 2] + sorted (sort coll)] + (is (= '(1 2 3) sorted)) + (is (= true (:my-meta (meta sorted))))))) + +(deftest test-sort-edge-cases + (testing "Sort edge cases" + ;; Large collection + (let [large (repeatedly 1000 #(rand-int 100)) + sorted (sort large)] + (is (= 1000 (count sorted))) + (is (apply <= sorted))) ; verify it's actually sorted + + ;; All equal elements + (is (= '(1 1 1 1) (sort [1 1 1 1]))) + + ;; Mixed positive/negative numbers + (is (= '(-3 -1 0 1 3) (sort [1 -1 3 0 -3]))))) + +(deftest test-sort-error-cases + (testing "Sort error cases" + ;; Invalid comparator (not a function) + (is (thrown? go/any (sort "not-a-function" [1 2 3]))) + + ;; Comparator returns non-numeric + (is (thrown? go/any + (sort (fn [a b] "not-a-number") [1 2 3]))) + + ;; Uncomparable types (this might throw or might have undefined behavior) + ;; Clojure's behavior here is to throw ClassCastException + (is (thrown? go/any + (sort [1 "a" :b]))))) + +(deftest test-compare-function + (testing "Compare function behavior" + ;; Numbers + (is (= -1 (compare 1 2))) + (is (= 0 (compare 2 2))) + (is (= 1 (compare 3 2))) + (is (= -1 (compare 1.5 2))) + (is (= 1 (compare 2.5 2))) + + ;; Strings + (is (= -1 (compare "a" "b"))) + (is (= 0 (compare "hello" "hello"))) + (is (= 1 (compare "z" "a"))) + + ;; Keywords + (is (= -1 (compare :a :b))) + (is (= 0 (compare :x :x))) + + ;; Symbols + (is (= -1 (compare 'a 'b))) + (is (= 0 (compare 'x 'x))) + + ;; nil handling + (is (= -1 (compare nil 1))) + (is (= -1 (compare nil "a"))) + (is (= -1 (compare nil :a))) + (is (= 0 (compare nil nil))) + (is (= 1 (compare 1 nil))) + + ;; Different numeric types + (is (= 0 (compare 1 1.0))) + (is (= 0 (compare 1.0 1))))) + +(deftest test-sort-maps-behavior + (testing "Sorting maps produces map entries" + (let [m {:b 2 :a 1 :c 3} + sorted (sort m)] + ;; Each element should be a map entry (vector of [k v]) + (is (every? vector? sorted)) + (is (every? #(= 2 (count %)) sorted)) + ;; Should be sorted by key + (is (= [[:a 1] [:b 2] [:c 3]] sorted))))) + +(deftest test-non-comparable-types + (testing "Non-comparable types throw errors" + ;; Lists are not comparable + (is (thrown? go/any (compare '(1 2) '(1 2)))) + (is (thrown? go/any (sort ['(1 2) '(3 4)]))) + + ;; Maps are not comparable + (is (thrown? go/any (compare {:a 1} {:b 2}))) + (is (thrown? go/any (sort [{:a 1} {:b 2}]))) + + ;; Sets are not comparable + (is (thrown? go/any (compare #{1 2} #{3 4}))) + (is (thrown? go/any (sort [#{1 2} #{3 4}]))) + + ;; Mixed incompatible types + (is (thrown? go/any (compare 1 :a))) + (is (thrown? go/any (compare "string" 'symbol))) + (is (thrown? go/any (compare :keyword [1 2 3]))))) + +(deftest test-vector-comparison + (testing "Vector comparison details" + ;; Vectors compare lexicographically + (is (= -1 (compare [1 2] [1 3]))) + (is (= 1 (compare [1 3] [1 2]))) + (is (= 0 (compare [1 2 3] [1 2 3]))) + + ;; Shorter vectors are less than longer vectors with same prefix + (is (= -1 (compare [1 2] [1 2 3]))) + (is (= 1 (compare [1 2 3] [1 2]))) + + ;; Nested vectors + (is (= -1 (compare [[1 2] [3 4]] [[1 2] [3 5]]))) + (is (= 0 (compare [[1 2] [3 4]] [[1 2] [3 4]]))) + + ;; SubVectors behave like vectors + (let [v [1 2 3 4 5] + sv1 (subvec v 1 3) ; [2 3] + sv2 (subvec v 2 4)] ; [3 4] + (is (= -1 (compare sv1 sv2))) + (is (= -1 (compare sv1 [3 4])))))) + +(deftest test-symbol-namespace-comparison + (testing "Symbols compare namespace-first" + ;; No namespace < with namespace + (is (= -1 (compare 'x 'a/x))) + (is (= 1 (compare 'a/x 'x))) + + ;; Different namespaces + (is (= -1 (compare 'a/x 'b/x))) + (is (= 1 (compare 'b/x 'a/x))) + + ;; Same namespace, different names + (is (= -1 (compare 'ns/a 'ns/b))) + (is (= 1 (compare 'ns/b 'ns/a))))) + +(deftest sort-by-clojuredocs-examples + (testing "Examples from ClojureDocs sort-by documentation" + + (let [words ["banana" "apple" "cherry" "date"]] + (is (= '("date" "apple" "banana" "cherry") + (sort-by count words)))) + + (let [words ["banana" "apple" "cherry" "date"]] + (is (= (sort-by count > words) + '("banana" "cherry" "apple" "date")))) + + (let [people [{:name "Alice" :age 30 :city "NYC"} + {:name "Bob" :age 25 :city "LA"} + {:name "Charlie" :age 35 :city "NYC"} + {:name "David" :age 25 :city "LA"}]] + (is (= (sort-by (juxt :city :age) people) + '({:name "Bob" :age 25 :city "LA"} + {:name "David" :age 25 :city "LA"} + {:name "Alice" :age 30 :city "NYC"} + {:name "Charlie" :age 35 :city "NYC"})))) + + (let [numbers [3 1 4 1 5 9 2 6]] + (is (= '(3 9 6 1 4 1 5 2) + (sort-by #(mod % 3) numbers)))) + + (let [items [nil "hello" 42 :keyword]] + (is (= (sort-by str items) + '(nil 42 :keyword "hello")))) + + (is (= (sort-by identity []) '())) + (is (= (sort-by count []) '())) + + (is (= (sort-by identity [42]) '(42))) + (is (= (sort-by count ["hello"]) '("hello"))))) + +;; Run tests +(run-tests)