From ed95525935c43fcefc4c56cfe9d09df498f802b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Hakl?= Date: Mon, 17 Aug 2026 18:11:47 +0200 Subject: [PATCH 01/10] BitVector class --- ListTalk/ListTalk.h | 1 + ListTalk/classes/BitVector.h | 25 ++++ meson.build | 1 + src/classes/BitVector.c | 223 +++++++++++++++++++++++++++++++++++ src/vm/base_env/base_env.c | 1 + tests/eval-objects.lt | 24 ++++ 6 files changed, 275 insertions(+) create mode 100644 ListTalk/classes/BitVector.h create mode 100644 src/classes/BitVector.c diff --git a/ListTalk/ListTalk.h b/ListTalk/ListTalk.h index 2f460e6..1ecacca 100644 --- a/ListTalk/ListTalk.h +++ b/ListTalk/ListTalk.h @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include diff --git a/ListTalk/classes/BitVector.h b/ListTalk/classes/BitVector.h new file mode 100644 index 0000000..4ac860e --- /dev/null +++ b/ListTalk/classes/BitVector.h @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) 2023 - 2026 Ales Hakl + */ + +#ifndef H__ListTalk__BitVector__ +#define H__ListTalk__BitVector__ + +#include + +#include +#include + +LT__BEGIN_DECLS + +LT_DECLARE_CLASS(LT_BitVector); + +LT_BitVector* LT_BitVector_new(size_t length, int fill); +size_t LT_BitVector_length(LT_BitVector* bitvector); +int LT_BitVector_at(LT_BitVector* bitvector, size_t index); +void LT_BitVector_atPut(LT_BitVector* bitvector, size_t index, int value); + +LT__END_DECLS + +#endif diff --git a/meson.build b/meson.build index 29fb440..b27f578 100644 --- a/meson.build +++ b/meson.build @@ -146,6 +146,7 @@ vm_lib = library( 'src/classes/List.c', 'src/classes/String.c', 'src/classes/Vector.c', + 'src/classes/BitVector.c', 'src/classes/ByteVector.c', 'src/classes/RandomAccessFile.c', 'src/classes/Builder.c', diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c new file mode 100644 index 0000000..d3a9fd0 --- /dev/null +++ b/src/classes/BitVector.c @@ -0,0 +1,223 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) 2023 - 2026 Ales Hakl + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct LT_BitVector_s { + LT_Object base; + size_t length; + uint8_t bytes[]; +}; + +static size_t byte_length_for_bits(size_t length){ + return length / 8 + (length % 8 != 0); +} + +static int bit_from_value(LT_Value value){ + if (LT_Value_is_boolean(value)){ + return LT_Value_boolean_value(value); + } + if (LT_SmallInteger_p(value)){ + int64_t integer = LT_SmallInteger_value(value); + + if (integer == 0 || integer == 1){ + return (int)integer; + } + } + LT_error("BitVector value must be 0, 1, #false, or #true"); +} + +static void BitVector_debugPrintOn(LT_Value value, FILE* stream){ + LT_BitVector* bitvector = LT_BitVector_from_value(value); + size_t i; + + fputs("#length; i++){ + fputc(' ', stream); + fputs(LT_BitVector_at(bitvector, i) ? "#true" : "#false", stream); + } + fputc('>', stream); +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_new, + "BitVector class>>new:", + "(self size)", + "Return a new false-filled bit vector of the requested size." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value size; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, size); + LT_ARG_END(cursor); + if (self != (LT_Value)(uintptr_t)<_BitVector_class){ + LT_error("new: class method is only supported on BitVector"); + } + return (LT_Value)(uintptr_t)LT_BitVector_new( + LT_Number_nonnegative_size_from_integer( + size, + "BitVector size out of bounds", + "BitVector size out of bounds" + ), + 0 + ); +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_new_filled, + "BitVector class>>new:filled:", + "(self size fill)", + "Return a new bit vector of the requested size and fill value." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value size; + LT_Value fill; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, size); + LT_OBJECT_ARG(cursor, fill); + LT_ARG_END(cursor); + if (self != (LT_Value)(uintptr_t)<_BitVector_class){ + LT_error("new:filled: class method is only supported on BitVector"); + } + return (LT_Value)(uintptr_t)LT_BitVector_new( + LT_Number_nonnegative_size_from_integer( + size, + "BitVector size out of bounds", + "BitVector size out of bounds" + ), + bit_from_value(fill) + ); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_at, + "BitVector>>at:", + "(self index)", + "Return the bit at index as a Boolean." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value index; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, index); + LT_ARG_END(cursor); + return LT_BitVector_at( + bitvector, + LT_Number_nonnegative_size_from_integer( + index, + "BitVector index out of bounds", + "BitVector index out of bounds" + ) + ) ? LT_TRUE : LT_FALSE; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_at_put, + "BitVector>>at:put:", + "(self index value)", + "Set the bit at index and return its Boolean value." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value index; + LT_Value value; + int bit; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, index); + LT_OBJECT_ARG(cursor, value); + LT_ARG_END(cursor); + bit = bit_from_value(value); + LT_BitVector_atPut( + bitvector, + LT_Number_nonnegative_size_from_integer( + index, + "BitVector index out of bounds", + "BitVector index out of bounds" + ), + bit + ); + return bit ? LT_TRUE : LT_FALSE; +} + +static LT_Method_Descriptor BitVector_methods[] = { + {"at:", &bitvector_method_at}, + {"at:put:", &bitvector_method_at_put}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +static LT_Method_Descriptor BitVector_class_methods[] = { + {"new:", &bitvector_class_method_new}, + {"new:filled:", &bitvector_class_method_new_filled}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +LT_DEFINE_CLASS(LT_BitVector) { + .superclass = <_Object_class, + .metaclass_superclass = <_Class_class, + .name = "BitVector", + .documentation = "Mutable indexed sequence of bits.", + .instance_size = sizeof(LT_BitVector), + .class_flags = LT_CLASS_FLAG_FLEXIBLE, + .debugPrintOn = BitVector_debugPrintOn, + .methods = BitVector_methods, + .class_methods = BitVector_class_methods, +}; + +LT_BitVector* LT_BitVector_new(size_t length, int fill){ + size_t byte_length = byte_length_for_bits(length); + LT_BitVector* bitvector = LT_Class_ALLOC_FLEXIBLE( + LT_BitVector, + byte_length + ); + + bitvector->length = length; + memset(bitvector->bytes, fill ? UINT8_MAX : 0, byte_length); + return bitvector; +} + +size_t LT_BitVector_length(LT_BitVector* bitvector){ + return bitvector->length; +} + +int LT_BitVector_at(LT_BitVector* bitvector, size_t index){ + if (index >= bitvector->length){ + LT_error("BitVector index out of bounds"); + } + return (bitvector->bytes[index / 8] >> (index % 8)) & 1; +} + +void LT_BitVector_atPut(LT_BitVector* bitvector, size_t index, int value){ + uint8_t mask; + + if (index >= bitvector->length){ + LT_error("BitVector index out of bounds"); + } + mask = (uint8_t)(UINT8_C(1) << (index % 8)); + if (value){ + bitvector->bytes[index / 8] |= mask; + } else { + bitvector->bytes[index / 8] &= (uint8_t)~mask; + } +} diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index 5471c61..1bfbb5b 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -140,6 +140,7 @@ static const struct LT_NativeClassBinding native_class_bindings[] = { {"MethodDescriptor", <_MethodDescriptor_class}, {"Vector", <_Vector_class}, {"VectorIterator", <_VectorIterator_class}, + {"BitVector", <_BitVector_class}, {"ByteVector", <_ByteVector_class}, {"ByteVectorIterator", <_ByteVectorIterator_class}, {"RandomAccessFile", <_RandomAccessFile_class}, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index 8f4f90f..1d55eaf 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -953,6 +953,30 @@ (let ((v #(1 2 3))) [v at: 1 put: 9] (check (eq? [v at: 1] 9) "Vector>>at:put: mutates element")) +(let ((bits [BitVector new: 10])) + (check (eq? [bits at: 0] #false) "BitVector class>>new: fills with false") + (check (eq? [bits at: 9] #false) "BitVector class>>new: preserves bit length") + (check (eq? [bits at: 1 put: 1] #true) + "BitVector>>at:put: accepts 1 and returns true") + (check (eq? [bits at: 1] #true) "BitVector>>at: returns a Boolean") + (check (eq? [bits at: 1 put: 0] #false) + "BitVector>>at:put: accepts 0 and returns false") + (check (eq? [bits at: 2 put: #true] #true) + "BitVector>>at:put: accepts #true") + (check (eq? [bits at: 2 put: #false] #false) + "BitVector>>at:put: accepts #false")) +(let ((ones [BitVector new: 9 filled: 1]) + (truths [BitVector new: 2 filled: #true]) + (falses [BitVector new: 2 filled: #false])) + (check (eq? [ones at: 8] #true) "BitVector class>>new:filled: accepts 1") + (check (eq? [truths at: 1] #true) "BitVector class>>new:filled: accepts #true") + (check (eq? [falses at: 1] #false) "BitVector class>>new:filled: accepts #false")) +(check-error-message "BitVector value must be 0, 1, #false, or #true" + "BitVector rejects other stored values" + [[BitVector new: 1] at: 0 put: 2]) +(check-error-message "BitVector index out of bounds" + "BitVector checks logical rather than byte bounds" + [[BitVector new: 1] at: 1]) (let ((v #(3 1 2 1))) (check (equal? [v sort] #(1 1 2 3)) "Vector>>sort returns sorted fresh vector") (check (equal? v #(3 1 2 1)) "Vector>>sort does not mutate receiver")) From bbd233617e3560d29cdf906987668d80dfa31444 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Hakl?= Date: Mon, 17 Aug 2026 18:17:56 +0200 Subject: [PATCH 02/10] BitVectorIterator --- ListTalk/classes/BitVector.h | 1 + src/classes/BitVector.c | 130 +++++++++++++++++++++++++++++++++++ src/vm/base_env/base_env.c | 1 + tests/eval-objects.lt | 24 +++++++ 4 files changed, 156 insertions(+) diff --git a/ListTalk/classes/BitVector.h b/ListTalk/classes/BitVector.h index 4ac860e..6d6244a 100644 --- a/ListTalk/classes/BitVector.h +++ b/ListTalk/classes/BitVector.h @@ -14,6 +14,7 @@ LT__BEGIN_DECLS LT_DECLARE_CLASS(LT_BitVector); +LT_DECLARE_CLASS(LT_BitVectorIterator); LT_BitVector* LT_BitVector_new(size_t length, int fill); size_t LT_BitVector_length(LT_BitVector* bitvector); diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index d3a9fd0..d440a54 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,12 @@ struct LT_BitVector_s { uint8_t bytes[]; }; +struct LT_BitVectorIterator_s { + LT_Object base; + LT_BitVector* bitvector; + size_t index; +}; + static size_t byte_length_for_bits(size_t length){ return length / 8 + (length % 8 != 0); } @@ -51,6 +58,88 @@ static void BitVector_debugPrintOn(LT_Value value, FILE* stream){ fputc('>', stream); } +static void BitVectorIterator_debugPrintOn(LT_Value value, FILE* stream){ + LT_BitVectorIterator* iterator = LT_BitVectorIterator_from_value(value); + + fprintf( + stream, + "#", + (void*)iterator, + iterator->index + ); +} + +static LT_Value BitVectorIterator_current(LT_BitVectorIterator* iterator){ + if (iterator->index >= LT_BitVector_length(iterator->bitvector)){ + LT_error("BitVectorIterator is not positioned"); + } + return LT_BitVector_at(iterator->bitvector, iterator->index) + ? LT_TRUE + : LT_FALSE; +} + +LT_DEFINE_PRIMITIVE( + bitvector_iterator_method_this, + "BitVectorIterator>>this", + "(self)", + "Return the current bit as a Boolean." +){ + LT_Value cursor = arguments; + LT_BitVectorIterator* iterator; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG( + cursor, + iterator, + LT_BitVectorIterator*, + LT_BitVectorIterator_from_value + ); + LT_ARG_END(cursor); + return BitVectorIterator_current(iterator); +} + +LT_DEFINE_PRIMITIVE( + bitvector_iterator_method_has_this, + "BitVectorIterator>>hasThis?", + "(self)", + "Return true when the iterator has a current bit." +){ + LT_Value cursor = arguments; + LT_BitVectorIterator* iterator; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG( + cursor, + iterator, + LT_BitVectorIterator*, + LT_BitVectorIterator_from_value + ); + LT_ARG_END(cursor); + return iterator->index < LT_BitVector_length(iterator->bitvector) + ? LT_TRUE + : LT_FALSE; +} + +LT_DEFINE_PRIMITIVE( + bitvector_iterator_method_next, + "BitVectorIterator>>next!", + "(self)", + "Advance the iterator and return receiver." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_BitVectorIterator* iterator; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_ARG_END(cursor); + iterator = LT_BitVectorIterator_from_value(self); + if (iterator->index < LT_BitVector_length(iterator->bitvector)){ + iterator->index++; + } + return self; +} + LT_DEFINE_PRIMITIVE( bitvector_class_method_new, "BitVector class>>new:", @@ -161,9 +250,40 @@ LT_DEFINE_PRIMITIVE( return bit ? LT_TRUE : LT_FALSE; } +LT_DEFINE_PRIMITIVE( + bitvector_method_as_iterator, + "BitVector>>asIterator", + "(self)", + "Return an iterator over the bits as Booleans." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_BitVectorIterator* iterator; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + if (LT_BitVector_length(bitvector) == 0){ + return (LT_Value)(uintptr_t)LT_EmptyIterator_instance(); + } + + iterator = LT_Class_ALLOC(LT_BitVectorIterator); + iterator->bitvector = bitvector; + iterator->index = 0; + return (LT_Value)(uintptr_t)iterator; +} + +static LT_Method_Descriptor BitVectorIterator_methods[] = { + {"this", &bitvector_iterator_method_this}, + {"hasThis?", &bitvector_iterator_method_has_this}, + {"next!", &bitvector_iterator_method_next}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + static LT_Method_Descriptor BitVector_methods[] = { {"at:", &bitvector_method_at}, {"at:put:", &bitvector_method_at_put}, + {"asIterator", &bitvector_method_as_iterator}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; @@ -173,6 +293,16 @@ static LT_Method_Descriptor BitVector_class_methods[] = { LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; +LT_DEFINE_CLASS(LT_BitVectorIterator) { + .superclass = <_Iterator_class, + .metaclass_superclass = <_Class_class, + .name = "BitVectorIterator", + .documentation = "Iterator over bit vector elements.", + .instance_size = sizeof(LT_BitVectorIterator), + .debugPrintOn = BitVectorIterator_debugPrintOn, + .methods = BitVectorIterator_methods, +}; + LT_DEFINE_CLASS(LT_BitVector) { .superclass = <_Object_class, .metaclass_superclass = <_Class_class, diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index 1bfbb5b..92529ad 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -141,6 +141,7 @@ static const struct LT_NativeClassBinding native_class_bindings[] = { {"Vector", <_Vector_class}, {"VectorIterator", <_VectorIterator_class}, {"BitVector", <_BitVector_class}, + {"BitVectorIterator", <_BitVectorIterator_class}, {"ByteVector", <_ByteVector_class}, {"ByteVectorIterator", <_ByteVectorIterator_class}, {"RandomAccessFile", <_RandomAccessFile_class}, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index 1d55eaf..ff520d5 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -971,6 +971,30 @@ (check (eq? [ones at: 8] #true) "BitVector class>>new:filled: accepts 1") (check (eq? [truths at: 1] #true) "BitVector class>>new:filled: accepts #true") (check (eq? [falses at: 1] #false) "BitVector class>>new:filled: accepts #false")) +(let ((bits [BitVector new: 3]) + (iterator #nil)) + [bits at: 1 put: #true] + (set! iterator [bits asIterator]) + (check (eq? [iterator class] BitVectorIterator) + "BitVector>>asIterator returns a BitVectorIterator") + (check (eq? [iterator this] #false) + "BitVectorIterator>>this returns false as a Boolean") + (check (eq? [iterator next!] iterator) + "BitVectorIterator>>next! returns receiver") + (check (eq? [iterator this] #true) + "BitVectorIterator>>this returns true as a Boolean") + [iterator next!] + [iterator next!] + (check (not [iterator hasThis?]) + "BitVectorIterator is exhausted at the bit length") + (check-error-message "BitVectorIterator is not positioned" + "BitVectorIterator>>this rejects an exhausted iterator" + [iterator this])) +(check (equal? [[[BitVector new: 4 filled: 1] asIterator] asList] + '(#true #true #true #true)) + "BitVectorIterator integrates with Iterator>>asList") +(check (eq? [[[BitVector new: 0] asIterator] class] EmptyIterator) + "An empty BitVector uses EmptyIterator") (check-error-message "BitVector value must be 0, 1, #false, or #true" "BitVector rejects other stored values" [[BitVector new: 1] at: 0 put: 2]) From d010525383ef8bd9ce9a9fbb6072b787296c8c4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Hakl?= Date: Mon, 17 Aug 2026 18:26:46 +0200 Subject: [PATCH 03/10] bitvector conversion functions --- src/classes/BitVector.c | 422 ++++++++++++++++++++++++++++++++++++++++ tests/eval-objects.lt | 50 +++++ 2 files changed, 472 insertions(+) diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index d440a54..6c5209e 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -3,15 +3,20 @@ * Copyright (c) 2023 - 2026 Ales Hakl */ +#include #include #include +#include #include #include +#include +#include #include #include #include #include #include +#include #include #include @@ -46,6 +51,102 @@ static int bit_from_value(LT_Value value){ LT_error("BitVector value must be 0, 1, #false, or #true"); } +static void require_bitvector_class(LT_Value self, const char* selector){ + (void)selector; + if (self != (LT_Value)(uintptr_t)<_BitVector_class){ + LT_error("Class method is only supported on BitVector"); + } +} + +static LT_BitVector* bitvector_from_be_bytes(LT_ByteVector* bytes, + size_t bit_length){ + LT_BitVector* bitvector = LT_BitVector_new(bit_length, 0); + size_t byte_length = LT_ByteVector_length(bytes); + size_t i; + + for (i = 0; i < byte_length; i++){ + uint8_t byte = LT_ByteVector_at(bytes, byte_length - i - 1); + size_t destination = i * 8; + size_t remaining; + + if (destination >= bit_length){ + break; + } + remaining = bit_length - destination; + if (remaining < 8){ + byte &= (uint8_t)(UINT8_C(1) << remaining) - 1; + } + bitvector->bytes[i] = byte; + } + return bitvector; +} + +static LT_BitVector* bitvector_from_le_bytes(LT_ByteVector* bytes){ + size_t byte_length = LT_ByteVector_length(bytes); + LT_BitVector* bitvector; + size_t i; + + if (byte_length > SIZE_MAX / 8){ + LT_error("ByteVector is too large for a BitVector"); + } + bitvector = LT_BitVector_new(byte_length * 8, 0); + for (i = 0; i < byte_length; i++){ + bitvector->bytes[i] = LT_ByteVector_at(bytes, i); + } + return bitvector; +} + +static LT_ByteVector* bitvector_as_le_bytes(LT_BitVector* bitvector){ + size_t byte_length = byte_length_for_bits(bitvector->length); + LT_ByteVector* bytes = LT_ByteVector_new_filled(byte_length, 0); + size_t i; + + for (i = 0; i < byte_length; i++){ + uint8_t byte = bitvector->bytes[i]; + + if (i + 1 == byte_length && bitvector->length % 8 != 0){ + byte &= (uint8_t)(UINT8_C(1) << (bitvector->length % 8)) - 1; + } + LT_ByteVector_atPut(bytes, i, byte); + } + return bytes; +} + +static LT_ByteVector* bitvector_as_be_bytes(LT_BitVector* bitvector){ + LT_ByteVector* little_endian = bitvector_as_le_bytes(bitvector); + size_t length = LT_ByteVector_length(little_endian); + LT_ByteVector* big_endian = LT_ByteVector_new_filled(length, 0); + size_t i; + + for (i = 0; i < length; i++){ + LT_ByteVector_atPut( + big_endian, + i, + LT_ByteVector_at(little_endian, length - i - 1) + ); + } + return big_endian; +} + +static size_t unsigned_bit_length(LT_ByteVector* bytes){ + size_t byte_length = LT_ByteVector_length(bytes); + uint8_t first; + size_t high_bits = 0; + + if (byte_length == 0){ + return 1; + } + first = LT_ByteVector_at(bytes, 0); + while (first != 0){ + first >>= 1; + high_bits++; + } + if (high_bits == 0){ + return 1; + } + return (byte_length - 1) * 8 + high_bits; +} + static void BitVector_debugPrintOn(LT_Value value, FILE* stream){ LT_BitVector* bitvector = LT_BitVector_from_value(value); size_t i; @@ -140,6 +241,208 @@ LT_DEFINE_PRIMITIVE( return self; } +LT_DEFINE_PRIMITIVE( + bitvector_class_method_from_unsigned_integer, + "BitVector class>>fromUnsignedInteger:", + "(self integer)", + "Return the shortest bit vector representing a non-negative integer." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value integer; + LT_ByteVector* bytes; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, integer); + LT_ARG_END(cursor); + require_bitvector_class(self, "fromUnsignedInteger:"); + bytes = LT_ByteVector_from_value(LT_SEND(integer, "toBytes")); + return (LT_Value)(uintptr_t)bitvector_from_be_bytes( + bytes, + unsigned_bit_length(bytes) + ); +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_from_unsigned_integer_size, + "BitVector class>>fromUnsignedInteger:size:", + "(self integer size)", + "Return an exact-size bit vector representing a non-negative integer." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value integer; + LT_Value size_value; + size_t size; + size_t byte_length; + LT_ByteVector* bytes; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, integer); + LT_OBJECT_ARG(cursor, size_value); + LT_ARG_END(cursor); + require_bitvector_class(self, "fromUnsignedInteger:size:"); + size = LT_Number_nonnegative_size_from_integer( + size_value, + "BitVector size out of bounds", + "BitVector size out of bounds" + ); + byte_length = byte_length_for_bits(size); + bytes = LT_ByteVector_from_value(LT_SEND( + integer, + "toBytes:", + LT_Number_smallinteger_from_size( + byte_length, + "BitVector size out of bounds" + ) + )); + if (size % 8 != 0 + && (LT_ByteVector_at(bytes, 0) >> (size % 8)) != 0){ + LT_error("Unsigned integer does not fit requested BitVector size"); + } + return (LT_Value)(uintptr_t)bitvector_from_be_bytes(bytes, size); +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_from_integer_size, + "BitVector class>>fromInteger:size:", + "(self integer size)", + "Return an exact-size two's-complement bit vector." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value integer; + LT_Value size_value; + size_t size; + size_t byte_length; + LT_ByteVector* bytes; + uint8_t high_byte; + uint8_t value_mask; + uint8_t extension_mask; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, integer); + LT_OBJECT_ARG(cursor, size_value); + LT_ARG_END(cursor); + require_bitvector_class(self, "fromInteger:size:"); + size = LT_Number_nonnegative_size_from_integer( + size_value, + "BitVector size out of bounds", + "BitVector size out of bounds" + ); + if (size == 0){ + LT_error("Signed BitVector size must be positive"); + } + byte_length = byte_length_for_bits(size); + bytes = LT_ByteVector_from_value(LT_SEND( + integer, + "toTwosComplement:", + LT_Number_smallinteger_from_size( + byte_length, + "BitVector size out of bounds" + ) + )); + high_byte = LT_ByteVector_at(bytes, 0); + if (size % 8 != 0){ + value_mask = (uint8_t)(UINT8_C(1) << (size % 8)) - 1; + extension_mask = (uint8_t)~value_mask; + if ((high_byte & (UINT8_C(1) << ((size - 1) % 8))) != 0){ + if ((high_byte & extension_mask) != extension_mask){ + LT_error("Integer does not fit requested BitVector size"); + } + } else if ((high_byte & extension_mask) != 0){ + LT_error("Integer does not fit requested BitVector size"); + } + } + return (LT_Value)(uintptr_t)bitvector_from_be_bytes(bytes, size); +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_from_list, + "BitVector class>>fromList:", + "(self list)", + "Return a bit vector whose index order matches the list." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value list; + LT_Value rest; + size_t length = 0; + size_t index = 0; + LT_BitVector* bitvector; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, list); + LT_ARG_END(cursor); + require_bitvector_class(self, "fromList:"); + if (!LT_List_p(list) || !LT_List_proper_p(list)){ + LT_error("BitVector class>>fromList: requires a proper List"); + } + rest = list; + while (rest != LT_NIL){ + if (length == SIZE_MAX){ + LT_error("List is too large for a BitVector"); + } + length++; + rest = LT_cdr(rest); + } + bitvector = LT_BitVector_new(length, 0); + rest = list; + while (rest != LT_NIL){ + LT_BitVector_atPut(bitvector, index++, bit_from_value(LT_car(rest))); + rest = LT_cdr(rest); + } + return (LT_Value)(uintptr_t)bitvector; +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_from_le_bytevector, + "BitVector class>>fromLEByteVector:", + "(self bytevector)", + "Return a bit vector decoded from little-endian bytes." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_ByteVector* bytes; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_GENERIC_ARG(cursor, bytes, LT_ByteVector*, LT_ByteVector_from_value); + LT_ARG_END(cursor); + require_bitvector_class(self, "fromLEByteVector:"); + return (LT_Value)(uintptr_t)bitvector_from_le_bytes(bytes); +} + +LT_DEFINE_PRIMITIVE( + bitvector_class_method_from_be_bytevector, + "BitVector class>>fromBEByteVector:", + "(self bytevector)", + "Return a bit vector decoded from big-endian bytes." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_ByteVector* bytes; + size_t byte_length; + (void)tail_call_unwind_marker; + + LT_OBJECT_ARG(cursor, self); + LT_GENERIC_ARG(cursor, bytes, LT_ByteVector*, LT_ByteVector_from_value); + LT_ARG_END(cursor); + require_bitvector_class(self, "fromBEByteVector:"); + byte_length = LT_ByteVector_length(bytes); + if (byte_length > SIZE_MAX / 8){ + LT_error("ByteVector is too large for a BitVector"); + } + return (LT_Value)(uintptr_t)bitvector_from_be_bytes( + bytes, + byte_length * 8 + ); +} + LT_DEFINE_PRIMITIVE( bitvector_class_method_new, "BitVector class>>new:", @@ -250,6 +553,114 @@ LT_DEFINE_PRIMITIVE( return bit ? LT_TRUE : LT_FALSE; } +LT_DEFINE_PRIMITIVE( + bitvector_method_as_unsigned_integer, + "BitVector>>asUnsignedInteger", + "(self)", + "Return the bits interpreted as an unsigned integer." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_ByteVector* bytes; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + bytes = bitvector_as_be_bytes(bitvector); + return LT_SEND( + (LT_Value)(uintptr_t)<_Integer_class, + "fromBytes:", + (LT_Value)(uintptr_t)bytes + ); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_as_signed_integer, + "BitVector>>asSignedInteger", + "(self)", + "Return the bits interpreted as a two's-complement integer." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_ByteVector* bytes; + size_t remainder; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + if (bitvector->length == 0){ + return LT_SmallInteger_new(0); + } + bytes = bitvector_as_be_bytes(bitvector); + remainder = bitvector->length % 8; + if (remainder != 0 + && LT_BitVector_at(bitvector, bitvector->length - 1)){ + uint8_t high_byte = LT_ByteVector_at(bytes, 0); + uint8_t value_mask = (uint8_t)(UINT8_C(1) << remainder) - 1; + + LT_ByteVector_atPut(bytes, 0, high_byte | (uint8_t)~value_mask); + } + return LT_SEND( + (LT_Value)(uintptr_t)<_Integer_class, + "fromTwosComplement:", + (LT_Value)(uintptr_t)bytes + ); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_as_list, + "BitVector>>asList", + "(self)", + "Return the bits as a list of Booleans in index order." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_ListBuilder* builder; + size_t i; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + builder = LT_ListBuilder_new(); + for (i = 0; i < bitvector->length; i++){ + LT_ListBuilder_append( + builder, + LT_BitVector_at(bitvector, i) ? LT_TRUE : LT_FALSE + ); + } + return LT_ListBuilder_value(builder); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_as_le_bytevector, + "BitVector>>asLEByteVector", + "(self)", + "Return the bits packed into little-endian bytes." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)bitvector_as_le_bytes(bitvector); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_as_be_bytevector, + "BitVector>>asBEByteVector", + "(self)", + "Return the bits packed into big-endian bytes." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)bitvector_as_be_bytes(bitvector); +} + LT_DEFINE_PRIMITIVE( bitvector_method_as_iterator, "BitVector>>asIterator", @@ -283,11 +694,22 @@ static LT_Method_Descriptor BitVectorIterator_methods[] = { static LT_Method_Descriptor BitVector_methods[] = { {"at:", &bitvector_method_at}, {"at:put:", &bitvector_method_at_put}, + {"asUnsignedInteger", &bitvector_method_as_unsigned_integer}, + {"asSignedInteger", &bitvector_method_as_signed_integer}, + {"asList", &bitvector_method_as_list}, + {"asLEByteVector", &bitvector_method_as_le_bytevector}, + {"asBEByteVector", &bitvector_method_as_be_bytevector}, {"asIterator", &bitvector_method_as_iterator}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; static LT_Method_Descriptor BitVector_class_methods[] = { + {"fromUnsignedInteger:", &bitvector_class_method_from_unsigned_integer}, + {"fromUnsignedInteger:size:", &bitvector_class_method_from_unsigned_integer_size}, + {"fromInteger:size:", &bitvector_class_method_from_integer_size}, + {"fromList:", &bitvector_class_method_from_list}, + {"fromLEByteVector:", &bitvector_class_method_from_le_bytevector}, + {"fromBEByteVector:", &bitvector_class_method_from_be_bytevector}, {"new:", &bitvector_class_method_new}, {"new:filled:", &bitvector_class_method_new_filled}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index ff520d5..f536113 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -995,6 +995,56 @@ "BitVectorIterator integrates with Iterator>>asList") (check (eq? [[[BitVector new: 0] asIterator] class] EmptyIterator) "An empty BitVector uses EmptyIterator") +(check (equal? [[BitVector fromUnsignedInteger: 0] asList] '(#false)) + "BitVector class>>fromUnsignedInteger: represents zero with one bit") +(check (equal? [[BitVector fromUnsignedInteger: 13] asList] + '(#true #false #true #true)) + "BitVector class>>fromUnsignedInteger: uses least-significant-bit-first indexes") +(check (equal? [[BitVector fromUnsignedInteger: 13 size: 6] asList] + '(#true #false #true #true #false #false)) + "BitVector class>>fromUnsignedInteger:size: zero-extends to exact size") +(check (= [[BitVector fromUnsignedInteger: #x10000000000000003] + asUnsignedInteger] + #x10000000000000003) + "BitVector unsigned integer conversion supports big integers") +(check (= [[BitVector fromUnsignedInteger: 0 size: 0] asUnsignedInteger] 0) + "Zero has an empty exact-width unsigned representation") +(check-error-message "Unsigned integer does not fit requested BitVector size" + "BitVector class>>fromUnsignedInteger:size: checks bit overflow" + [BitVector fromUnsignedInteger: 8 size: 3]) +(check (equal? [[BitVector fromInteger: -3 size: 4] asList] + '(#true #false #true #true)) + "BitVector class>>fromInteger:size: uses two's complement") +(check (= [[BitVector fromInteger: -3 size: 4] asSignedInteger] -3) + "BitVector>>asSignedInteger sign-extends partial bytes") +(check (= [[BitVector fromInteger: 7 size: 4] asSignedInteger] 7) + "BitVector signed conversion preserves positive values") +(check (= [[BitVector fromInteger: -128 size: 8] asSignedInteger] -128) + "BitVector signed conversion handles whole bytes") +(check-error-message "Integer does not fit requested BitVector size" + "BitVector class>>fromInteger:size: rejects positive overflow" + [BitVector fromInteger: 8 size: 4]) +(check-error-message "Integer does not fit requested BitVector size" + "BitVector class>>fromInteger:size: rejects negative overflow" + [BitVector fromInteger: -9 size: 4]) +(check (equal? [[BitVector fromList: '(1 #false #true 0)] asList] + '(#true #false #true #false)) + "BitVector list conversions normalize values to Booleans") +(let ((bytes (bytevector 1 128))) + (check (equal? [[BitVector fromLEByteVector: bytes] asLEByteVector] bytes) + "BitVector little-endian ByteVector conversion round-trips") + (check (equal? [[BitVector fromLEByteVector: bytes] asBEByteVector] + (bytevector 128 1)) + "BitVector converts little-endian bytes to big-endian bytes") + (check (equal? [[BitVector fromBEByteVector: bytes] asBEByteVector] bytes) + "BitVector big-endian ByteVector conversion round-trips") + (check (= [[BitVector fromBEByteVector: bytes] asUnsignedInteger] #x0180) + "BitVector big-endian bytes have the expected unsigned value")) +(let ((partial [BitVector fromUnsignedInteger: 13 size: 6])) + (check (equal? [partial asLEByteVector] (bytevector 13)) + "BitVector>>asLEByteVector zeroes unused high bits") + (check (equal? [partial asBEByteVector] (bytevector 13)) + "BitVector>>asBEByteVector packs a partial leading byte")) (check-error-message "BitVector value must be 0, 1, #false, or #true" "BitVector rejects other stored values" [[BitVector new: 1] at: 0 put: 2]) From 248e3b90c063a2ec62ee3495fd77f55f7d82996a Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Mon, 17 Aug 2026 21:51:27 +0200 Subject: [PATCH 04/10] operations on bitvectors --- src/classes/BitVector.c | 292 ++++++++++++++++++++++++++++++++++++++++ tests/eval-objects.lt | 37 +++++ 2 files changed, 329 insertions(+) diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index 6c5209e..556c8fa 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,38 @@ static int bit_from_value(LT_Value value){ LT_error("BitVector value must be 0, 1, #false, or #true"); } +static void require_same_length(LT_BitVector* left, LT_BitVector* right){ + if (left->length != right->length){ + LT_error("BitVector lengths must match"); + } +} + +static void require_span(LT_BitVector* bitvector, size_t at, size_t length){ + if (at > bitvector->length || length > bitvector->length - at){ + LT_error("BitVector range out of bounds"); + } +} + +static LT_BitVector* bitvector_binary(LT_BitVector* left, + LT_BitVector* right, + int operation){ + LT_BitVector* result; + size_t i; + + require_same_length(left, right); + result = LT_BitVector_new(left->length, 0); + for (i = 0; i < left->length; i++){ + int a = LT_BitVector_at(left, i); + int b = LT_BitVector_at(right, i); + int value = operation == 0 ? (a & b) + : operation == 1 ? (a | b) + : (a ^ b); + + LT_BitVector_atPut(result, i, value); + } + return result; +} + static void require_bitvector_class(LT_Value self, const char* selector){ (void)selector; if (self != (LT_Value)(uintptr_t)<_BitVector_class){ @@ -553,6 +586,256 @@ LT_DEFINE_PRIMITIVE( return bit ? LT_TRUE : LT_FALSE; } +LT_DEFINE_PRIMITIVE( + bitvector_method_pop_count, + "BitVector>>popCount", + "(self)", + "Return the number of set bits." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + size_t count = 0; + size_t i; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + for (i = 0; i < bitvector->length; i++){ + count += (size_t)LT_BitVector_at(bitvector, i); + } + return LT_Number_smallinteger_from_size( + count, + "BitVector population count does not fit fixnum" + ); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_at_length, + "BitVector>>at:length:", + "(self index length)", + "Return a copy of a range of bits." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value index_value; + LT_Value length_value; + LT_BitVector* result; + size_t index; + size_t length; + size_t i; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, index_value); + LT_OBJECT_ARG(cursor, length_value); + LT_ARG_END(cursor); + index = LT_Number_nonnegative_size_from_integer( + index_value, "BitVector range out of bounds", "BitVector range out of bounds" + ); + length = LT_Number_nonnegative_size_from_integer( + length_value, "BitVector range out of bounds", "BitVector range out of bounds" + ); + require_span(bitvector, index, length); + result = LT_BitVector_new(length, 0); + for (i = 0; i < length; i++){ + LT_BitVector_atPut(result, i, LT_BitVector_at(bitvector, index + i)); + } + return (LT_Value)(uintptr_t)result; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_put_at, + "BitVector>>put:at:", + "(self value index)", + "Put a bit or all bits of a BitVector starting at index and return value." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value value; + LT_Value index_value; + size_t index; + size_t i; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, value); + LT_OBJECT_ARG(cursor, index_value); + LT_ARG_END(cursor); + index = LT_Number_nonnegative_size_from_integer( + index_value, "BitVector index out of bounds", "BitVector index out of bounds" + ); + if (LT_BitVector_p(value)){ + LT_BitVector* source = LT_BitVector_from_value(value); + LT_BitVector* source_copy = NULL; + + require_span(bitvector, index, source->length); + if (source == bitvector && source->length != 0){ + source_copy = LT_BitVector_new(source->length, 0); + for (i = 0; i < source->length; i++){ + LT_BitVector_atPut(source_copy, i, LT_BitVector_at(source, i)); + } + source = source_copy; + } + for (i = 0; i < source->length; i++){ + LT_BitVector_atPut(bitvector, index + i, LT_BitVector_at(source, i)); + } + } else { + LT_BitVector_atPut(bitvector, index, bit_from_value(value)); + } + return value; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_combine_with_using, + "BitVector>>combineWith:using:", + "(self other combination)", + "Combine equal-length BitVectors using a four-entry truth table or function." +){ + LT_Value cursor = arguments; + LT_BitVector* left; + LT_BitVector* right; + LT_Value combination; + LT_BitVector* result; + size_t i; + (void)tail_call_unwind_marker; + + LT_GENERIC_ARG(cursor, left, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, right, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, combination); + LT_ARG_END(cursor); + require_same_length(left, right); + if (LT_BitVector_p(combination) + && LT_BitVector_length(LT_BitVector_from_value(combination)) != 4){ + LT_error("BitVector combination table must have four elements"); + } + if (LT_Vector_p(combination) + && LT_Vector_length(LT_Vector_from_value(combination)) != 4){ + LT_error("BitVector combination table must have four elements"); + } + result = LT_BitVector_new(left->length, 0); + for (i = 0; i < left->length; i++){ + int a = LT_BitVector_at(left, i); + int b = LT_BitVector_at(right, i); + size_t table_index = (size_t)(a * 2 + b); + LT_Value value; + + if (LT_BitVector_p(combination)){ + value = LT_BitVector_at(LT_BitVector_from_value(combination), table_index) + ? LT_TRUE : LT_FALSE; + } else if (LT_Vector_p(combination)){ + value = LT_Vector_at(LT_Vector_from_value(combination), table_index); + } else { + value = LT_APPLY( + combination, + a ? LT_TRUE : LT_FALSE, + b ? LT_TRUE : LT_FALSE + ); + } + LT_BitVector_atPut(result, i, bit_from_value(value)); + } + return (LT_Value)(uintptr_t)result; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_and, + "BitVector>>and:", + "(self other)", + "Return the bitwise conjunction with another BitVector." +){ + LT_Value cursor = arguments; + LT_BitVector* left; + LT_BitVector* right; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, left, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, right, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)bitvector_binary(left, right, 0); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_or, + "BitVector>>or:", + "(self other)", + "Return the bitwise disjunction with another BitVector." +){ + LT_Value cursor = arguments; + LT_BitVector* left; + LT_BitVector* right; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, left, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, right, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)bitvector_binary(left, right, 1); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_xor, + "BitVector>>xor:", + "(self other)", + "Return the bitwise exclusive disjunction with another BitVector." +){ + LT_Value cursor = arguments; + LT_BitVector* left; + LT_BitVector* right; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, left, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, right, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)bitvector_binary(left, right, 2); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_not, + "BitVector>>not", + "(self)", + "Return a BitVector with every bit inverted." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_BitVector* result; + size_t i; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + result = LT_BitVector_new(bitvector->length, 0); + for (i = 0; i < bitvector->length; i++){ + LT_BitVector_atPut(result, i, !LT_BitVector_at(bitvector, i)); + } + return (LT_Value)(uintptr_t)result; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_select_or, + "BitVector>>select:or:", + "(self whenTrue whenFalse)", + "Select bits from the first or second BitVector according to receiver." +){ + LT_Value cursor = arguments; + LT_BitVector* selector; + LT_BitVector* when_true; + LT_BitVector* when_false; + LT_BitVector* result; + size_t i; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, selector, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, when_true, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, when_false, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + require_same_length(selector, when_true); + require_same_length(selector, when_false); + result = LT_BitVector_new(selector->length, 0); + for (i = 0; i < selector->length; i++){ + LT_BitVector_atPut( + result, + i, + LT_BitVector_at(selector, i) + ? LT_BitVector_at(when_true, i) + : LT_BitVector_at(when_false, i) + ); + } + return (LT_Value)(uintptr_t)result; +} + LT_DEFINE_PRIMITIVE( bitvector_method_as_unsigned_integer, "BitVector>>asUnsignedInteger", @@ -693,7 +976,16 @@ static LT_Method_Descriptor BitVectorIterator_methods[] = { static LT_Method_Descriptor BitVector_methods[] = { {"at:", &bitvector_method_at}, + {"at:length:", &bitvector_method_at_length}, {"at:put:", &bitvector_method_at_put}, + {"put:at:", &bitvector_method_put_at}, + {"popCount", &bitvector_method_pop_count}, + {"select:or:", &bitvector_method_select_or}, + {"and:", &bitvector_method_and}, + {"or:", &bitvector_method_or}, + {"xor:", &bitvector_method_xor}, + {"not", &bitvector_method_not}, + {"combineWith:using:", &bitvector_method_combine_with_using}, {"asUnsignedInteger", &bitvector_method_as_unsigned_integer}, {"asSignedInteger", &bitvector_method_as_signed_integer}, {"asList", &bitvector_method_as_list}, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index f536113..e585cc8 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1051,6 +1051,43 @@ (check-error-message "BitVector index out of bounds" "BitVector checks logical rather than byte bounds" [[BitVector new: 1] at: 1]) +(let ((a [BitVector fromList: '(0 1 1 0)]) + (b [BitVector fromList: '(0 0 1 1)])) + (check (= [a popCount] 2) "BitVector>>popCount counts set bits") + (check (equal? [[a at: 1 length: 2] asList] '(#true #true)) + "BitVector>>at:length: returns a bit slice") + (check (equal? [[a and: b] asList] '(#false #false #true #false)) + "BitVector>>and: combines corresponding bits") + (check (equal? [[a or: b] asList] '(#false #true #true #true)) + "BitVector>>or: combines corresponding bits") + (check (equal? [[a xor: b] asList] '(#false #true #false #true)) + "BitVector>>xor: combines corresponding bits") + (check (equal? [[a not] asList] '(#true #false #false #true)) + "BitVector>>not inverts every logical bit") + (check (equal? [[a select: a or: b] asList] + '(#false #true #true #true)) + "BitVector>>select:or: selects corresponding bits") + (check (equal? [[a combineWith: b using: [BitVector fromList: '(0 1 1 0)]] + asList] + '(#false #true #false #true)) + "BitVector>>combineWith:using: accepts a BitVector truth table") + (check (equal? [[a combineWith: b using: #(0 0 0 1)] asList] + '(#false #false #true #false)) + "BitVector>>combineWith:using: accepts a Vector truth table") + (check (equal? [[a combineWith: b using: (lambda (x y) (or x y))] asList] + '(#false #true #true #true)) + "BitVector>>combineWith:using: accepts a two-argument function")) +(let ((destination [BitVector new: 6])) + [destination put: [BitVector fromList: '(1 0 1)] at: 2] + (check (equal? [destination asList] + '(#false #false #true #false #true #false)) + "BitVector>>put:at: inserts a BitVector")) +(check-error-message "BitVector range out of bounds" + "BitVector>>at:length: checks the complete range" + [[BitVector new: 3] at: 2 length: 2]) +(check-error-message "BitVector lengths must match" + "BitVector binary operations require matching lengths" + [[BitVector new: 2] and: [BitVector new: 3]]) (let ((v #(3 1 2 1))) (check (equal? [v sort] #(1 1 2 3)) "Vector>>sort returns sorted fresh vector") (check (equal? v #(3 1 2 1)) "Vector>>sort does not mutate receiver")) From a32b2ae1ab58262ecd6c967e779233b8d2648f1a Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Mon, 17 Aug 2026 22:12:51 +0200 Subject: [PATCH 05/10] fix duplicated method --- src/classes/BitVector.c | 73 ++++++++++------------------------------- tests/eval-objects.lt | 4 +-- 2 files changed, 20 insertions(+), 57 deletions(-) diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index 556c8fa..2de59ec 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -560,30 +560,36 @@ LT_DEFINE_PRIMITIVE( bitvector_method_at_put, "BitVector>>at:put:", "(self index value)", - "Set the bit at index and return its Boolean value." + "Put a bit or all bits of a BitVector starting at index and return value." ){ LT_Value cursor = arguments; LT_BitVector* bitvector; LT_Value index; LT_Value value; - int bit; + size_t at; + size_t i; (void)tail_call_unwind_marker; LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); LT_OBJECT_ARG(cursor, index); LT_OBJECT_ARG(cursor, value); LT_ARG_END(cursor); - bit = bit_from_value(value); - LT_BitVector_atPut( - bitvector, - LT_Number_nonnegative_size_from_integer( - index, - "BitVector index out of bounds", - "BitVector index out of bounds" - ), - bit + at = LT_Number_nonnegative_size_from_integer( + index, + "BitVector index out of bounds", + "BitVector index out of bounds" ); - return bit ? LT_TRUE : LT_FALSE; + if (LT_BitVector_p(value)){ + LT_BitVector* source = LT_BitVector_from_value(value); + + require_span(bitvector, at, source->length); + for (i = 0; i < source->length; i++){ + LT_BitVector_atPut(bitvector, at + i, LT_BitVector_at(source, i)); + } + return value; + } + LT_BitVector_atPut(bitvector, at, bit_from_value(value)); + return LT_BitVector_at(bitvector, at) ? LT_TRUE : LT_FALSE; } LT_DEFINE_PRIMITIVE( @@ -643,48 +649,6 @@ LT_DEFINE_PRIMITIVE( return (LT_Value)(uintptr_t)result; } -LT_DEFINE_PRIMITIVE( - bitvector_method_put_at, - "BitVector>>put:at:", - "(self value index)", - "Put a bit or all bits of a BitVector starting at index and return value." -){ - LT_Value cursor = arguments; - LT_BitVector* bitvector; - LT_Value value; - LT_Value index_value; - size_t index; - size_t i; - (void)tail_call_unwind_marker; - - LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); - LT_OBJECT_ARG(cursor, value); - LT_OBJECT_ARG(cursor, index_value); - LT_ARG_END(cursor); - index = LT_Number_nonnegative_size_from_integer( - index_value, "BitVector index out of bounds", "BitVector index out of bounds" - ); - if (LT_BitVector_p(value)){ - LT_BitVector* source = LT_BitVector_from_value(value); - LT_BitVector* source_copy = NULL; - - require_span(bitvector, index, source->length); - if (source == bitvector && source->length != 0){ - source_copy = LT_BitVector_new(source->length, 0); - for (i = 0; i < source->length; i++){ - LT_BitVector_atPut(source_copy, i, LT_BitVector_at(source, i)); - } - source = source_copy; - } - for (i = 0; i < source->length; i++){ - LT_BitVector_atPut(bitvector, index + i, LT_BitVector_at(source, i)); - } - } else { - LT_BitVector_atPut(bitvector, index, bit_from_value(value)); - } - return value; -} - LT_DEFINE_PRIMITIVE( bitvector_method_combine_with_using, "BitVector>>combineWith:using:", @@ -978,7 +942,6 @@ static LT_Method_Descriptor BitVector_methods[] = { {"at:", &bitvector_method_at}, {"at:length:", &bitvector_method_at_length}, {"at:put:", &bitvector_method_at_put}, - {"put:at:", &bitvector_method_put_at}, {"popCount", &bitvector_method_pop_count}, {"select:or:", &bitvector_method_select_or}, {"and:", &bitvector_method_and}, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index e585cc8..a8efd3d 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1078,10 +1078,10 @@ '(#false #true #true #true)) "BitVector>>combineWith:using: accepts a two-argument function")) (let ((destination [BitVector new: 6])) - [destination put: [BitVector fromList: '(1 0 1)] at: 2] + [destination at: 2 put: [BitVector fromList: '(1 0 1)]] (check (equal? [destination asList] '(#false #false #true #false #true #false)) - "BitVector>>put:at: inserts a BitVector")) + "BitVector>>at:put: inserts a BitVector")) (check-error-message "BitVector range out of bounds" "BitVector>>at:length: checks the complete range" [[BitVector new: 3] at: 2 length: 2]) From c894a847e092655b9d93ee3007f673765eb5c60d Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Mon, 17 Aug 2026 23:05:45 +0200 Subject: [PATCH 06/10] rename select to choose --- src/classes/BitVector.c | 6 +++--- tests/eval-objects.lt | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index 2de59ec..53d1dba 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -769,8 +769,8 @@ LT_DEFINE_PRIMITIVE( } LT_DEFINE_PRIMITIVE( - bitvector_method_select_or, - "BitVector>>select:or:", + bitvector_method_choose_or, + "BitVector>>choose:or:", "(self whenTrue whenFalse)", "Select bits from the first or second BitVector according to receiver." ){ @@ -943,7 +943,7 @@ static LT_Method_Descriptor BitVector_methods[] = { {"at:length:", &bitvector_method_at_length}, {"at:put:", &bitvector_method_at_put}, {"popCount", &bitvector_method_pop_count}, - {"select:or:", &bitvector_method_select_or}, + {"choose:or:", &bitvector_method_choose_or}, {"and:", &bitvector_method_and}, {"or:", &bitvector_method_or}, {"xor:", &bitvector_method_xor}, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index a8efd3d..ecc841f 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1064,9 +1064,9 @@ "BitVector>>xor: combines corresponding bits") (check (equal? [[a not] asList] '(#true #false #false #true)) "BitVector>>not inverts every logical bit") - (check (equal? [[a select: a or: b] asList] + (check (equal? [[a choose: a or: b] asList] '(#false #true #true #true)) - "BitVector>>select:or: selects corresponding bits") + "BitVector>>choose:or: selects corresponding bits") (check (equal? [[a combineWith: b using: [BitVector fromList: '(0 1 1 0)]] asList] '(#false #true #false #true)) From a2e066d7971badba6785945c02b4810381594e43 Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Tue, 18 Aug 2026 01:18:20 +0200 Subject: [PATCH 07/10] more bitvector operations --- src/classes/BitVector.c | 381 ++++++++++++++++++++++++++++++++++++++++ tests/eval-objects.lt | 52 ++++++ 2 files changed, 433 insertions(+) diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index 53d1dba..8810064 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -84,6 +84,38 @@ static LT_BitVector* bitvector_binary(LT_BitVector* left, return result; } +static size_t BitVector_hash(LT_Value value){ + LT_BitVector* bitvector = LT_BitVector_from_value(value); + size_t hash = (size_t)0x9e3779b1 ^ bitvector->length; + size_t i; + + for (i = 0; i < bitvector->length; i++){ + hash = (hash * (size_t)33) ^ (size_t)LT_BitVector_at(bitvector, i); + } + return hash; +} + +static int BitVector_equal_p(LT_Value left, LT_Value right){ + LT_BitVector* a; + LT_BitVector* b; + size_t i; + + if (!LT_BitVector_p(right)){ + return 0; + } + a = LT_BitVector_from_value(left); + b = LT_BitVector_from_value(right); + if (a->length != b->length){ + return 0; + } + for (i = 0; i < a->length; i++){ + if (LT_BitVector_at(a, i) != LT_BitVector_at(b, i)){ + return 0; + } + } + return 1; +} + static void require_bitvector_class(LT_Value self, const char* selector){ (void)selector; if (self != (LT_Value)(uintptr_t)<_BitVector_class){ @@ -800,6 +832,338 @@ LT_DEFINE_PRIMITIVE( return (LT_Value)(uintptr_t)result; } +LT_DEFINE_PRIMITIVE( + bitvector_method_length, + "BitVector>>length", + "(self)", + "Return the number of bits." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return LT_Number_smallinteger_from_size( + bitvector->length, + "BitVector length does not fit fixnum" + ); +} + +static LT_BitVector* bitvector_shift(LT_BitVector* source, + size_t amount, + int left){ + LT_BitVector* result = LT_BitVector_new(source->length, 0); + size_t i; + + if (amount >= source->length){ + return result; + } + if (left){ + for (i = amount; i < source->length; i++){ + LT_BitVector_atPut(result, i, LT_BitVector_at(source, i - amount)); + } + } else { + for (i = 0; i < source->length - amount; i++){ + LT_BitVector_atPut(result, i, LT_BitVector_at(source, i + amount)); + } + } + return result; +} + +#define DEFINE_BITVECTOR_SHIFT_METHOD(c_name, selector, direction) \ +LT_DEFINE_PRIMITIVE( \ + c_name, selector, "(self amount)", "Return a logically shifted BitVector." \ +){ \ + LT_Value cursor = arguments; \ + LT_BitVector* bitvector; \ + LT_Value amount_value; \ + size_t amount; \ + (void)tail_call_unwind_marker; \ + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); \ + LT_OBJECT_ARG(cursor, amount_value); \ + LT_ARG_END(cursor); \ + amount = LT_Number_nonnegative_size_from_integer( \ + amount_value, "BitVector shift must be nonnegative", \ + "BitVector shift out of range" \ + ); \ + return (LT_Value)(uintptr_t)bitvector_shift(bitvector, amount, direction); \ +} + +DEFINE_BITVECTOR_SHIFT_METHOD( + bitvector_method_shift_left, "BitVector>>shiftLeft:", 1 +) +DEFINE_BITVECTOR_SHIFT_METHOD( + bitvector_method_shift_right, "BitVector>>shiftRight:", 0 +) + +static LT_Value bitvector_rotate_primitive(LT_Value arguments, int left){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value amount_value; + LT_BitVector* result; + size_t amount; + size_t i; + + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, amount_value); + LT_ARG_END(cursor); + amount = LT_Number_nonnegative_size_from_integer( + amount_value, + "BitVector rotation must be nonnegative", + "BitVector rotation out of range" + ); + result = LT_BitVector_new(bitvector->length, 0); + if (bitvector->length == 0){ + return (LT_Value)(uintptr_t)result; + } + amount %= bitvector->length; + for (i = 0; i < bitvector->length; i++){ + size_t destination = left + ? (i + amount) % bitvector->length + : (i + bitvector->length - amount) % bitvector->length; + LT_BitVector_atPut(result, destination, LT_BitVector_at(bitvector, i)); + } + return (LT_Value)(uintptr_t)result; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_rotate_left, + "BitVector>>rotateLeft:", + "(self amount)", + "Return a left-rotated BitVector." +){ + (void)tail_call_unwind_marker; + return bitvector_rotate_primitive(arguments, 1); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_rotate_right, + "BitVector>>rotateRight:", + "(self amount)", + "Return a right-rotated BitVector." +){ + (void)tail_call_unwind_marker; + return bitvector_rotate_primitive(arguments, 0); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_append, + "BitVector>>append:", + "(self other)", + "Return the concatenation of receiver and another BitVector." +){ + LT_Value cursor = arguments; + LT_BitVector* left; + LT_BitVector* right; + LT_BitVector* result; + size_t i; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, left, LT_BitVector*, LT_BitVector_from_value); + LT_GENERIC_ARG(cursor, right, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + if (right->length > SIZE_MAX - left->length){ + LT_error("Concatenated BitVector is too large"); + } + result = LT_BitVector_new(left->length + right->length, 0); + for (i = 0; i < left->length; i++){ + LT_BitVector_atPut(result, i, LT_BitVector_at(left, i)); + } + for (i = 0; i < right->length; i++){ + LT_BitVector_atPut(result, left->length + i, LT_BitVector_at(right, i)); + } + return (LT_Value)(uintptr_t)result; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_resize_filled, + "BitVector>>resize:filled:", + "(self length fill)", + "Return a resized copy, filling newly added bits with fill." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value length_value; + LT_Value fill_value; + LT_BitVector* result; + size_t length; + size_t copied; + size_t i; + int fill; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, length_value); + LT_OBJECT_ARG(cursor, fill_value); + LT_ARG_END(cursor); + length = LT_Number_nonnegative_size_from_integer( + length_value, "BitVector size out of bounds", "BitVector size out of bounds" + ); + fill = bit_from_value(fill_value); + result = LT_BitVector_new(length, fill); + copied = length < bitvector->length ? length : bitvector->length; + for (i = 0; i < copied; i++){ + LT_BitVector_atPut(result, i, LT_BitVector_at(bitvector, i)); + } + return (LT_Value)(uintptr_t)result; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_rank, + "BitVector>>rank:", + "(self index)", + "Return the number of set bits before index." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value index_value; + size_t index; + size_t count = 0; + size_t i; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, index_value); + LT_ARG_END(cursor); + index = LT_Number_nonnegative_size_from_integer( + index_value, "BitVector rank index out of bounds", + "BitVector rank index out of bounds" + ); + if (index > bitvector->length){ + LT_error("BitVector rank index out of bounds"); + } + for (i = 0; i < index; i++){ + count += (size_t)LT_BitVector_at(bitvector, i); + } + return LT_Number_smallinteger_from_size(count, "BitVector rank does not fit fixnum"); +} + +static LT_Value bitvector_find_set_bit(LT_BitVector* bitvector, + size_t start, + size_t ordinal, + int use_ordinal){ + size_t i; + size_t seen = 0; + + for (i = start; i < bitvector->length; i++){ + if (LT_BitVector_at(bitvector, i)){ + if (!use_ordinal || seen == ordinal){ + return LT_Number_smallinteger_from_size( + i, "BitVector index does not fit fixnum" + ); + } + seen++; + } + } + return LT_NIL; +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_select, + "BitVector>>select:", + "(self ordinal)", + "Return the index of the zero-based ordinal set bit, or nil." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value ordinal_value; + size_t ordinal; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, ordinal_value); + LT_ARG_END(cursor); + ordinal = LT_Number_nonnegative_size_from_integer( + ordinal_value, "BitVector select ordinal out of bounds", + "BitVector select ordinal out of bounds" + ); + return bitvector_find_set_bit(bitvector, 0, ordinal, 1); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_first_set_bit, + "BitVector>>firstSetBit", + "(self)", + "Return the first set-bit index, or nil." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + return bitvector_find_set_bit(bitvector, 0, 0, 0); +} + +LT_DEFINE_PRIMITIVE( + bitvector_method_next_set_bit_after, + "BitVector>>nextSetBitAfter:", + "(self index)", + "Return the next set-bit index strictly after index, or nil." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_Value index_value; + size_t index; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_OBJECT_ARG(cursor, index_value); + LT_ARG_END(cursor); + index = LT_Number_nonnegative_size_from_integer( + index_value, "BitVector index out of bounds", "BitVector index out of bounds" + ); + if (index >= bitvector->length){ + LT_error("BitVector index out of bounds"); + } + return bitvector_find_set_bit(bitvector, index + 1, 0, 0); +} + +static LT_Value bitvector_predicate(LT_BitVector* bitvector, int kind){ + size_t count = 0; + size_t i; + for (i = 0; i < bitvector->length; i++){ + count += (size_t)LT_BitVector_at(bitvector, i); + } + if (kind == 0){ + return count != 0 ? LT_TRUE : LT_FALSE; + } + if (kind == 1){ + return count == bitvector->length ? LT_TRUE : LT_FALSE; + } + return count == 0 ? LT_TRUE : LT_FALSE; +} + +#define DEFINE_BITVECTOR_PREDICATE(c_name, selector, kind_value) \ +LT_DEFINE_PRIMITIVE(c_name, selector, "(self)", "Query the receiver bits."){ \ + LT_Value cursor = arguments; \ + LT_BitVector* bitvector; \ + (void)tail_call_unwind_marker; \ + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); \ + LT_ARG_END(cursor); \ + return bitvector_predicate(bitvector, kind_value); \ +} + +DEFINE_BITVECTOR_PREDICATE(bitvector_method_any, "BitVector>>any?", 0) +DEFINE_BITVECTOR_PREDICATE(bitvector_method_all, "BitVector>>all?", 1) +DEFINE_BITVECTOR_PREDICATE(bitvector_method_none, "BitVector>>none?", 2) + +LT_DEFINE_PRIMITIVE( + bitvector_method_reverse, + "BitVector>>reverse", + "(self)", + "Return a BitVector with reversed index order." +){ + LT_Value cursor = arguments; + LT_BitVector* bitvector; + LT_BitVector* result; + size_t i; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, bitvector, LT_BitVector*, LT_BitVector_from_value); + LT_ARG_END(cursor); + result = LT_BitVector_new(bitvector->length, 0); + for (i = 0; i < bitvector->length; i++){ + LT_BitVector_atPut( + result, bitvector->length - i - 1, LT_BitVector_at(bitvector, i) + ); + } + return (LT_Value)(uintptr_t)result; +} + LT_DEFINE_PRIMITIVE( bitvector_method_as_unsigned_integer, "BitVector>>asUnsignedInteger", @@ -939,6 +1303,7 @@ static LT_Method_Descriptor BitVectorIterator_methods[] = { }; static LT_Method_Descriptor BitVector_methods[] = { + {"length", &bitvector_method_length}, {"at:", &bitvector_method_at}, {"at:length:", &bitvector_method_at_length}, {"at:put:", &bitvector_method_at_put}, @@ -949,6 +1314,20 @@ static LT_Method_Descriptor BitVector_methods[] = { {"xor:", &bitvector_method_xor}, {"not", &bitvector_method_not}, {"combineWith:using:", &bitvector_method_combine_with_using}, + {"shiftLeft:", &bitvector_method_shift_left}, + {"shiftRight:", &bitvector_method_shift_right}, + {"rotateLeft:", &bitvector_method_rotate_left}, + {"rotateRight:", &bitvector_method_rotate_right}, + {"append:", &bitvector_method_append}, + {"resize:filled:", &bitvector_method_resize_filled}, + {"rank:", &bitvector_method_rank}, + {"select:", &bitvector_method_select}, + {"firstSetBit", &bitvector_method_first_set_bit}, + {"nextSetBitAfter:", &bitvector_method_next_set_bit_after}, + {"any?", &bitvector_method_any}, + {"all?", &bitvector_method_all}, + {"none?", &bitvector_method_none}, + {"reverse", &bitvector_method_reverse}, {"asUnsignedInteger", &bitvector_method_as_unsigned_integer}, {"asSignedInteger", &bitvector_method_as_signed_integer}, {"asList", &bitvector_method_as_list}, @@ -987,6 +1366,8 @@ LT_DEFINE_CLASS(LT_BitVector) { .documentation = "Mutable indexed sequence of bits.", .instance_size = sizeof(LT_BitVector), .class_flags = LT_CLASS_FLAG_FLEXIBLE, + .hash = BitVector_hash, + .equal_p = BitVector_equal_p, .debugPrintOn = BitVector_debugPrintOn, .methods = BitVector_methods, .class_methods = BitVector_class_methods, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index ecc841f..e69ecce 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1088,6 +1088,58 @@ (check-error-message "BitVector lengths must match" "BitVector binary operations require matching lengths" [[BitVector new: 2] and: [BitVector new: 3]]) +(let ((bits [BitVector fromList: '(1 1 0 0)])) + (check (= [bits length] 4) "BitVector>>length returns the logical bit length") + (check (equal? bits [BitVector fromList: '(1 1 0 0)]) + "BitVectors compare structurally") + (check (not (equal? bits [BitVector fromList: '(1 1 0)])) + "BitVector equality includes length") + (check (equal? [[bits shiftLeft: 1] asList] + '(#false #true #true #false)) + "BitVector>>shiftLeft: preserves length") + (check (equal? [[bits shiftRight: 1] asList] + '(#true #false #false #false)) + "BitVector>>shiftRight: preserves length") + (check (equal? [[bits shiftLeft: 9] asList] + '(#false #false #false #false)) + "BitVector shifts beyond length produce zero bits") + (check (equal? [[bits rotateLeft: 1] asList] + '(#false #true #true #false)) + "BitVector>>rotateLeft: wraps bits") + (check (equal? [[bits rotateRight: 1] asList] + '(#true #false #false #true)) + "BitVector>>rotateRight: wraps bits") + (check (equal? [[bits reverse] asList] + '(#false #false #true #true)) + "BitVector>>reverse reverses index order") + (check (equal? [[bits append: [BitVector fromList: '(0 1)]] asList] + '(#true #true #false #false #false #true)) + "BitVector>>append: concatenates in index order") + (check (equal? [[bits resize: 6 filled: 1] asList] + '(#true #true #false #false #true #true)) + "BitVector>>resize:filled: extends with the requested bit") + (check (equal? [[bits resize: 2 filled: 0] asList] '(#true #true)) + "BitVector>>resize:filled: truncates high-index bits") + (check (= [bits rank: 3] 2) "BitVector>>rank: counts bits before index") + (check (= [bits select: 0] 0) "BitVector>>select: uses zero-based ordinals") + (check (= [bits select: 1] 1) "BitVector>>select: finds later set bits") + (check (eq? [bits select: 2] #nil) "BitVector>>select: returns nil when absent") + (check (= [bits firstSetBit] 0) "BitVector>>firstSetBit finds the first bit") + (check (= [bits nextSetBitAfter: 0] 1) + "BitVector>>nextSetBitAfter: finds the following bit") + (check (eq? [bits nextSetBitAfter: 1] #nil) + "BitVector>>nextSetBitAfter: returns nil when absent") + (check [bits any?] "BitVector>>any? detects a set bit") + (check (not [bits all?]) "BitVector>>all? detects an unset bit") + (check (not [bits none?]) "BitVector>>none? detects a set bit")) +(let ((empty [BitVector new: 0])) + (check (not [empty any?]) "Empty BitVector does not satisfy any?") + (check [empty all?] "Empty BitVector vacuously satisfies all?") + (check [empty none?] "Empty BitVector satisfies none?") + (check (eq? [empty firstSetBit] #nil) + "Empty BitVector has no first set bit") + (check (equal? [[empty rotateLeft: 3] asList] '()) + "Rotating an empty BitVector is defined")) (let ((v #(3 1 2 1))) (check (equal? [v sort] #(1 1 2 3)) "Vector>>sort returns sorted fresh vector") (check (equal? v #(3 1 2 1)) "Vector>>sort does not mutate receiver")) From dd1b9ae2a3eb9b73e6944f50c780bac77a4c86e3 Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Tue, 18 Aug 2026 19:32:51 +0200 Subject: [PATCH 08/10] integer truth tables --- src/classes/BitVector.c | 17 ++++++++++++++++- tests/eval-objects.lt | 9 +++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index 8810064..2693691 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -685,13 +685,15 @@ LT_DEFINE_PRIMITIVE( bitvector_method_combine_with_using, "BitVector>>combineWith:using:", "(self other combination)", - "Combine equal-length BitVectors using a four-entry truth table or function." + "Combine BitVectors using a four-entry table, 4-bit integer, or function." ){ LT_Value cursor = arguments; LT_BitVector* left; LT_BitVector* right; LT_Value combination; LT_BitVector* result; + int64_t integer_table = 0; + int combination_is_integer; size_t i; (void)tail_call_unwind_marker; @@ -708,6 +710,17 @@ LT_DEFINE_PRIMITIVE( && LT_Vector_length(LT_Vector_from_value(combination)) != 4){ LT_error("BitVector combination table must have four elements"); } + combination_is_integer = LT_Value_is_instance_of( + combination, + LT_STATIC_CLASS(LT_Integer) + ); + if (combination_is_integer){ + if (!LT_SmallInteger_p(combination) + || (integer_table = LT_SmallInteger_value(combination)) < 0 + || integer_table > 15){ + LT_error("BitVector integer combination table must be between 0 and 15"); + } + } result = LT_BitVector_new(left->length, 0); for (i = 0; i < left->length; i++){ int a = LT_BitVector_at(left, i); @@ -720,6 +733,8 @@ LT_DEFINE_PRIMITIVE( ? LT_TRUE : LT_FALSE; } else if (LT_Vector_p(combination)){ value = LT_Vector_at(LT_Vector_from_value(combination), table_index); + } else if (combination_is_integer){ + value = (integer_table >> table_index) & 1 ? LT_TRUE : LT_FALSE; } else { value = LT_APPLY( combination, diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index e69ecce..1ebda8f 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1074,9 +1074,18 @@ (check (equal? [[a combineWith: b using: #(0 0 0 1)] asList] '(#false #false #true #false)) "BitVector>>combineWith:using: accepts a Vector truth table") + (check (equal? [[a combineWith: b using: 6] asList] + '(#false #true #false #true)) + "BitVector>>combineWith:using: accepts an integer truth table") (check (equal? [[a combineWith: b using: (lambda (x y) (or x y))] asList] '(#false #true #true #true)) "BitVector>>combineWith:using: accepts a two-argument function")) +(check-error-message "BitVector integer combination table must be between 0 and 15" + "BitVector integer truth tables reject excess bits" + [[BitVector new: 1] combineWith: [BitVector new: 1] using: 16]) +(check-error-message "BitVector integer combination table must be between 0 and 15" + "BitVector integer truth tables reject negative values" + [[BitVector new: 1] combineWith: [BitVector new: 1] using: -1]) (let ((destination [BitVector new: 6])) [destination at: 2 put: [BitVector fromList: '(1 0 1)]] (check (equal? [destination asList] From ef65892a1967fb147114a348aa7783ed8cdd8287 Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Tue, 18 Aug 2026 19:40:15 +0200 Subject: [PATCH 09/10] C accessor functions for BitVectors --- ListTalk/classes/BitVector.h | 9 +++++ src/classes/BitVector.c | 64 ++++++++++++++++++++++++++++++++++++ tests/c_api_test.c | 42 +++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/ListTalk/classes/BitVector.h b/ListTalk/classes/BitVector.h index 6d6244a..c4291ad 100644 --- a/ListTalk/classes/BitVector.h +++ b/ListTalk/classes/BitVector.h @@ -17,9 +17,18 @@ LT_DECLARE_CLASS(LT_BitVector); LT_DECLARE_CLASS(LT_BitVectorIterator); LT_BitVector* LT_BitVector_new(size_t length, int fill); +/* Input arrays contain ceil(bit_length / 8) packed bytes. */ +LT_BitVector* LT_BitVector_from_le_uint8_array(const uint8_t* bytes, + size_t bit_length); +LT_BitVector* LT_BitVector_from_be_uint8_array(const uint8_t* bytes, + size_t bit_length); size_t LT_BitVector_length(LT_BitVector* bitvector); +size_t LT_BitVector_byte_length(LT_BitVector* bitvector); int LT_BitVector_at(LT_BitVector* bitvector, size_t index); void LT_BitVector_atPut(LT_BitVector* bitvector, size_t index, int value); +/* Output arrays must have LT_BitVector_byte_length(bitvector) bytes. */ +void LT_BitVector_to_le_uint8_array(LT_BitVector* bitvector, uint8_t* bytes); +void LT_BitVector_to_be_uint8_array(LT_BitVector* bitvector, uint8_t* bytes); LT__END_DECLS diff --git a/src/classes/BitVector.c b/src/classes/BitVector.c index 2693691..aa0ffc2 100644 --- a/src/classes/BitVector.c +++ b/src/classes/BitVector.c @@ -1400,10 +1400,45 @@ LT_BitVector* LT_BitVector_new(size_t length, int fill){ return bitvector; } +LT_BitVector* LT_BitVector_from_le_uint8_array(const uint8_t* bytes, + size_t bit_length){ + LT_BitVector* bitvector = LT_BitVector_new(bit_length, 0); + size_t i; + + for (i = 0; i < bit_length; i++){ + LT_BitVector_atPut( + bitvector, + i, + (bytes[i / 8] >> (i % 8)) & 1 + ); + } + return bitvector; +} + +LT_BitVector* LT_BitVector_from_be_uint8_array(const uint8_t* bytes, + size_t bit_length){ + LT_BitVector* bitvector = LT_BitVector_new(bit_length, 0); + size_t byte_length = byte_length_for_bits(bit_length); + size_t i; + + for (i = 0; i < bit_length; i++){ + LT_BitVector_atPut( + bitvector, + i, + (bytes[byte_length - i / 8 - 1] >> (i % 8)) & 1 + ); + } + return bitvector; +} + size_t LT_BitVector_length(LT_BitVector* bitvector){ return bitvector->length; } +size_t LT_BitVector_byte_length(LT_BitVector* bitvector){ + return byte_length_for_bits(bitvector->length); +} + int LT_BitVector_at(LT_BitVector* bitvector, size_t index){ if (index >= bitvector->length){ LT_error("BitVector index out of bounds"); @@ -1424,3 +1459,32 @@ void LT_BitVector_atPut(LT_BitVector* bitvector, size_t index, int value){ bitvector->bytes[index / 8] &= (uint8_t)~mask; } } + +void LT_BitVector_to_le_uint8_array(LT_BitVector* bitvector, uint8_t* bytes){ + size_t byte_length = byte_length_for_bits(bitvector->length); + size_t i; + + if (byte_length != 0){ + memset(bytes, 0, byte_length); + } + for (i = 0; i < bitvector->length; i++){ + if (LT_BitVector_at(bitvector, i)){ + bytes[i / 8] |= (uint8_t)(UINT8_C(1) << (i % 8)); + } + } +} + +void LT_BitVector_to_be_uint8_array(LT_BitVector* bitvector, uint8_t* bytes){ + size_t byte_length = byte_length_for_bits(bitvector->length); + size_t i; + + if (byte_length != 0){ + memset(bytes, 0, byte_length); + } + for (i = 0; i < bitvector->length; i++){ + if (LT_BitVector_at(bitvector, i)){ + bytes[byte_length - i / 8 - 1] |= + (uint8_t)(UINT8_C(1) << (i % 8)); + } + } +} diff --git a/tests/c_api_test.c b/tests/c_api_test.c index 2c8cae7..99cbd2a 100644 --- a/tests/c_api_test.c +++ b/tests/c_api_test.c @@ -4,6 +4,7 @@ */ #include +#include #include #include #include @@ -3604,6 +3605,46 @@ static int test_integer_from_intmax_c_api(void){ return 0; } +static int test_bitvector_uint8_array_conversions(void){ + const uint8_t little_endian[] = {0x5a, 0x03}; + const uint8_t big_endian[] = {0x03, 0x5a}; + LT_BitVector* from_little = LT_BitVector_from_le_uint8_array( + little_endian, + 10 + ); + LT_BitVector* from_big = LT_BitVector_from_be_uint8_array( + big_endian, + 10 + ); + uint8_t little_result[] = {0xff, 0xff}; + uint8_t big_result[] = {0xff, 0xff}; + int failed = 0; + size_t i; + + failed += expect( + LT_BitVector_length(from_little) == 10 + && LT_BitVector_byte_length(from_little) == 2, + "BitVector uint8 array conversion preserves partial-byte bit length" + ); + for (i = 0; i < 10; i++){ + failed += expect( + LT_BitVector_at(from_little, i) == LT_BitVector_at(from_big, i), + "BitVector little- and big-endian uint8 arrays represent equal bits" + ); + } + LT_BitVector_to_le_uint8_array(from_big, little_result); + LT_BitVector_to_be_uint8_array(from_little, big_result); + failed += expect( + memcmp(little_result, little_endian, sizeof(little_endian)) == 0, + "BitVector converts to a packed little-endian uint8 array" + ); + failed += expect( + memcmp(big_result, big_endian, sizeof(big_endian)) == 0, + "BitVector converts to a packed big-endian uint8 array" + ); + return failed; +} + static int test_integer_bytevector_conversions(void){ static const uint8_t zero[] = {0x00}; static const uint8_t unsigned_255[] = {0xff}; @@ -4370,6 +4411,7 @@ int main(void){ RUN_TEST(test_string_search_c_api_uses_codepoint_indexes); RUN_TEST(test_string_format_c_api); RUN_TEST(test_integer_from_intmax_c_api); + RUN_TEST(test_bitvector_uint8_array_conversions); RUN_TEST(test_integer_bytevector_conversions); RUN_TEST(test_string_split_c_api); RUN_TEST(test_file_stream_c_api_reads_writes_and_borrowed_close); From c767276f215221cbb94b1e45412fcab2e5093fab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 18 Aug 2026 18:11:41 +0000 Subject: [PATCH 10/10] Add missing BitVector.h include in base_env.c Co-authored-by: adh <44205+adh@users.noreply.github.com> --- src/vm/base_env/base_env.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index 92529ad..a43d4f8 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include