From f85198e3ee2dab775ab460613a8b54dd1377e881 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Mon, 14 Apr 2025 00:55:46 +0200 Subject: [PATCH] Added += and related as overloads. Updated tests and docs. Slice2 extracted to its own file. --- lib/std/collections/bitset.c3 | 116 ++++++++++++-- lib/std/core/allocators/arena_allocator.c3 | 55 +++++-- lib/std/core/array.c3 | 103 ++----------- lib/std/core/bitorder.c3 | 2 +- lib/std/core/dstring.c3 | 13 ++ lib/std/core/slice2d.c3 | 169 +++++++++++++++++++++ lib/std/math/bigint.c3 | 16 +- releasenotes.md | 4 +- src/compiler/enums.h | 12 +- src/compiler/parse_global.c | 40 +++++ src/compiler/sema_decls.c | 92 +++++++++-- src/compiler/sema_expr.c | 35 ++++- test/unit/stdlib/collections/bitset.c3 | 56 ++++++- test/unit/stdlib/core/slice2d.c3 | 19 +++ test/unit/stdlib/mem/arena_allocator.c3 | 19 +++ 15 files changed, 608 insertions(+), 143 deletions(-) create mode 100644 lib/std/core/slice2d.c3 create mode 100644 test/unit/stdlib/core/slice2d.c3 create mode 100644 test/unit/stdlib/mem/arena_allocator.c3 diff --git a/lib/std/collections/bitset.c3 b/lib/std/collections/bitset.c3 index 0ef405010..4d91721c5 100644 --- a/lib/std/collections/bitset.c3 +++ b/lib/std/collections/bitset.c3 @@ -1,18 +1,22 @@ +// Copyright (c) 2023-2025 C3 team. All rights reserved. +// Use of self source code is governed by the MIT license +// a copy of which can be found in the LICENSE_STDLIB file. <* - @require SIZE > 0 + @require SIZE > 0 : "The size of the bitset in bits must be at least 1" *> -module std::collections::bitset{SIZE}; +module std::collections::bitset {SIZE}; -alias Type = uint; - -const BITS = Type.sizeof * 8; +const BITS = uint.sizeof * 8; const SZ = (SIZE + BITS - 1) / BITS; struct BitSet { - Type[SZ] data; + uint[SZ] data; } +<* + @return "The number of bits set" +*> fn usz BitSet.cardinality(&self) { usz n; @@ -24,7 +28,11 @@ fn usz BitSet.cardinality(&self) } <* - @require i < SIZE + Set a bit in the bitset. + + @param i : "The index to set" + + @require i < SIZE : "Index was out of range" *> fn void BitSet.set(&self, usz i) { @@ -34,7 +42,86 @@ fn void BitSet.set(&self, usz i) } <* - @require i < SIZE + Perform xor over all bits, mutating itself + + @param set : "The bit set to xor with" + @return "The resulting bit set" +*> +macro BitSet BitSet.xor_self(&self, BitSet set) @operator(^=) +{ + foreach (i, &x : self.data) *x ^= set.data[i]; + return *self; +} + +<* + Perform xor over all bits, returning a new bit set. + + @param set : "The bit set to xor with" + @return "The resulting bit set" +*> +fn BitSet BitSet.xor(&self, BitSet set) @operator(^) +{ + BitSet new_set @noinit; + foreach (i, x : self.data) new_set.data[i] = x ^ set.data[i]; + return new_set; +} + +<* + Perform or over all bits, returning a new bit set. + + @param set : "The bit set to xor with" + @return "The resulting bit set" +*> +fn BitSet BitSet.or(&self, BitSet set) @operator(|) +{ + BitSet new_set @noinit; + foreach (i, x : self.data) new_set.data[i] = x | set.data[i]; + return new_set; +} + +<* + Perform or over all bits, mutating itself + + @param set : "The bit set to xor with" + @return "The resulting bit set" +*> +macro BitSet BitSet.or_self(&self, BitSet set) @operator(|=) +{ + foreach (i, &x : self.data) *x |= set.data[i]; + return *self; +} + +<* + Perform & over all bits, returning a new bit set. + + @param set : "The bit set to xor with" + @return "The resulting bit set" +*> +fn BitSet BitSet.and(&self, BitSet set) @operator(&) +{ + BitSet new_set @noinit; + foreach (i, x : self.data) new_set.data[i] = x & set.data[i]; + return new_set; +} + +<* + Perform & over all bits, mutating itself. + + @param set : "The bit set to xor with" + @return "The resulting bit set" +*> +macro BitSet BitSet.and_self(&self, BitSet set) @operator(&=) +{ + foreach (i, &x : self.data) *x &= set.data[i]; + return *self; +} + +<* + Unset (clear) a bit in the bitset. + + @param i : "The index to set" + + @require i < SIZE : "Index was out of range" *> fn void BitSet.unset(&self, usz i) { @@ -44,7 +131,11 @@ fn void BitSet.unset(&self, usz i) } <* - @require i < SIZE + Get a particular bit in the bitset + + @param i : "The index of the bit" + + @require i < SIZE : "Index was out of range" *> fn bool BitSet.get(&self, usz i) @operator([]) @inline { @@ -59,7 +150,12 @@ fn usz BitSet.len(&self) @operator(len) @inline } <* - @require i < SIZE + Change a particular bit in the bitset + + @param i : "The index of the bit" + @param value : "The value to set the bit to" + + @require i < SIZE : "Index was out of range" *> fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline { diff --git a/lib/std/core/allocators/arena_allocator.c3 b/lib/std/core/allocators/arena_allocator.c3 index 4cc8631c6..4168aea62 100644 --- a/lib/std/core/allocators/arena_allocator.c3 +++ b/lib/std/core/allocators/arena_allocator.c3 @@ -1,9 +1,13 @@ -// Copyright (c) 2023 Christoffer Lerno. All rights reserved. +// Copyright (c) 2023-2025 Christoffer Lerno. All rights reserved. // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. module std::core::mem::allocator; import std::math; +// The arena allocator allocates up to its maximum data +// and then fails to allocate more, returning out of memory. +// It supports mark and reset to mark. + struct ArenaAllocator (Allocator) { char[] data; @@ -12,6 +16,8 @@ struct ArenaAllocator (Allocator) <* Initialize a memory arena for use using the provided bytes. + + @param [inout] data : "The memory to use for the arena." *> fn ArenaAllocator* ArenaAllocator.init(&self, char[] data) { @@ -20,23 +26,44 @@ fn ArenaAllocator* ArenaAllocator.init(&self, char[] data) return self; } +<* + Reset the usage completely. +*> fn void ArenaAllocator.clear(&self) { self.used = 0; } -struct ArenaAllocatorHeader @local -{ - usz size; - char[*] data; -} +<* + Given some memory, create an arena allocator on the stack for it. + @param [inout] bytes : `The bytes to use` + + @return `An arena allocator using the bytes` +*> macro ArenaAllocator* wrap(char[] bytes) { return (ArenaAllocator){}.init(bytes); } <* + "Mark" the current state of the arena allocator by returning the use count. + + @return `The value to pass to 'reset' in order to reset to the current use.` +*> +fn usz ArenaAllocator.mark(&self) => self.used; + +<* + Reset to a previous mark. + + @param mark : `The previous mark.` + @require mark <= self.used : "Invalid mark - out of range" +*> +fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark; + +<* + Implements the Allocator interface method. + @require ptr != null *> fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic @@ -50,10 +77,10 @@ fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic } } -fn usz ArenaAllocator.mark(&self) => self.used; -fn void ArenaAllocator.reset(&self, usz mark) => self.used = mark; <* + Implements the Allocator interface method. + @require !alignment || math::is_power_of_2(alignment) @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` @require size > 0 @@ -77,6 +104,8 @@ fn void*? ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz a } <* + Implements the Allocator interface method. + @require !alignment || math::is_power_of_2(alignment) @require alignment <= mem::MAX_MEMORY_ALIGNMENT : `alignment too big` @require old_pointer != null @@ -111,4 +140,12 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen void* mem = self.acquire(size, NO_ZERO, alignment)!; mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT); return mem; -} \ No newline at end of file +} + +// Internal data + +struct ArenaAllocatorHeader @local +{ + usz size; + char[*] data; +} diff --git a/lib/std/core/array.c3 b/lib/std/core/array.c3 index 4175301c3..12fd61ab6 100644 --- a/lib/std/core/array.c3 +++ b/lib/std/core/array.c3 @@ -17,6 +17,14 @@ macro index_of(array, element) } <* + Slice a 2d array and create a Slice2d from it. + + @param array_ptr : "the pointer to create a slice from" + @param x : "The starting position of the slice x, optional" + @param y : "The starting position of the slice y, optional" + @param xlen : "The length of the slice in x, defaults to the length of the array" + @param ylen : "The length of the slice in y, defaults to the length of the array" + @return "A Slice2d from the array" @require @typekind(array_ptr) == POINTER @require @typekind(*array_ptr) == VECTOR || @typekind(*array_ptr) == ARRAY @require @typekind((*array_ptr)[0]) == VECTOR || @typekind((*array_ptr)[0]) == ARRAY @@ -81,97 +89,4 @@ macro concat(Allocator allocator, arr1, arr2) @nodiscard @require @typeis(arr1[0], $typeof(arr2[0])) : "Arrays must have the same type" @ensure return.len == arr1.len + arr2.len *> -macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2); - -module std::core::array::slice{Type}; - -struct Slice2d -{ - Type* ptr; - usz inner_len; - usz ystart; - usz ylen; - usz xstart; - usz xlen; -} - -fn usz Slice2d.len(&self) @operator(len) -{ - return self.ylen; -} - -fn usz Slice2d.count(&self) -{ - return self.ylen * self.xlen; -} - -macro void Slice2d.@each(&self; @body(usz[<2>], Type)) -{ - foreach (y, line : *self) - { - foreach (x, val : line) - { - @body({ x, y }, val); - } - } -} - -macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*)) -{ - foreach (y, line : *self) - { - foreach (x, &val : line) - { - @body({ x, y }, val); - } - } -} - -<* - @require idy >= 0 && idy < self.ylen -*> -macro Type[] Slice2d.get_row(self, usz idy) @operator([]) -{ - return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen]; -} - -macro Type Slice2d.get_coord(self, usz[<2>] coord) -{ - return *self.get_coord_ref(coord); -} - -macro Type Slice2d.get_xy(self, x, y) -{ - return *self.get_xy_ref(x, y); -} - -macro Type* Slice2d.get_xy_ref(self, x, y) -{ - return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x; -} - -macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord) -{ - return self.get_xy_ref(coord.x, coord.y); -} - -macro void Slice2d.set_coord(self, usz[<2>] coord, Type value) -{ - *self.get_coord_ref(coord) = value; -} - -macro void Slice2d.set_xy(self, x, y, Type value) -{ - *self.get_xy_ref(x, y) = value; -} - -<* - @require y >= 0 && y < self.ylen - @require x >= 0 && x < self.xlen -*> -fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0) -{ - if (xlen < 1) xlen = self.xlen + xlen; - if (ylen < 1) ylen = self.ylen + ylen; - return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen }; -} +macro tconcat(arr1, arr2) @nodiscard => concat(tmem, arr1, arr2); \ No newline at end of file diff --git a/lib/std/core/bitorder.c3 b/lib/std/core/bitorder.c3 index e645bf684..aac11326c 100644 --- a/lib/std/core/bitorder.c3 +++ b/lib/std/core/bitorder.c3 @@ -1,4 +1,4 @@ -// Copyright (c) 2023 Christoffer Lerno and contributors. All rights reserved. +// Copyright (c) 2023-2025 Christoffer Lerno and contributors. All rights reserved. // Use of this source code is governed by the MIT license // a copy of which can be found in the LICENSE_STDLIB file. module std::core::bitorder; diff --git a/lib/std/core/dstring.c3 b/lib/std/core/dstring.c3 index cedb31e1c..52838a2a7 100644 --- a/lib/std/core/dstring.c3 +++ b/lib/std/core/dstring.c3 @@ -1,12 +1,20 @@ module std::core::dstring; import std::io; +<* + The DString offers a dynamic string builder. +*> typedef DString (OutStream) = DStringOpaque*; typedef DStringOpaque = void; const usz MIN_CAPACITY @private = 16; <* + Initialize the DString with a particular allocator. + + @param [&inout] allocator : "The allocator to use" + @param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller" + @return "Return the DString itself" @require !self.data() : "String already initialized" *> fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY) @@ -20,6 +28,11 @@ fn DString DString.init(&self, Allocator allocator, usz capacity = MIN_CAPACITY) } <* + Initialize the DString with the temp allocator. Note that if the dstring is never + initialized, this is the allocator it will default to. + + @param capacity : "Starting capacity, defaults to MIN_CAPACITY and cannot be smaller" + @return "Return the DString itself" @require !self.data() : "String already initialized" *> fn DString DString.tinit(&self, usz capacity = MIN_CAPACITY) diff --git a/lib/std/core/slice2d.c3 b/lib/std/core/slice2d.c3 new file mode 100644 index 000000000..df368cba7 --- /dev/null +++ b/lib/std/core/slice2d.c3 @@ -0,0 +1,169 @@ +module std::core::array::slice {Type}; + +<* + A slice2d allows slicing an array like int[10][10] into an arbitrary "int[][]"-like counterpart + Typically you'd use array::slice2d(...) to create one. +*> +struct Slice2d +{ + Type* ptr; + usz inner_len; + usz ystart; + usz ylen; + usz xstart; + usz xlen; +} + +<* + @return `The length of the "outer" slice` +*> +fn usz Slice2d.len(&self) @operator(len) +{ + return self.ylen; +} + +<* + @return `The total number of elements.` +*> +fn usz Slice2d.count(&self) +{ + return self.ylen * self.xlen; +} + +<* + Step through each element of the slice. +*> +macro void Slice2d.@each(&self; @body(usz[<2>], Type)) +{ + foreach (y, line : *self) + { + foreach (x, val : line) + { + @body({ x, y }, val); + } + } +} + +<* + Step through each element of the slice *by reference* +*> +macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*)) +{ + foreach (y, line : *self) + { + foreach (x, &val : line) + { + @body({ x, y }, val); + } + } +} + +<* + Return a row as a slice. + + @param idy : "The row to return" + @return "The slice for the particular row" + @require idy >= 0 && idy < self.ylen +*> +macro Type[] Slice2d.get_row(self, usz idy) @operator([]) +{ + return (self.ptr + self.inner_len * (idy + self.ystart))[self.xstart:self.xlen]; +} + +<* + Get the value at a particular x/y position in the slice. + + @param coord : "The xy coordinate" + @return "The value at that coordinate" + @require coord.y >= 0 && coord.y < self.ylen : "y value out of range" + @require coord.x >= 0 && coord.x < self.xlen : "x value out of range" +*> +macro Type Slice2d.get_coord(self, usz[<2>] coord) +{ + return *self.get_coord_ref(coord); +} + +<* + Get a pointer to the value at a particular x/y position in the slice. + + @param coord : "The xy coordinate" + @return "A pointer to the value at that coordinate" + @require coord.y >= 0 && coord.y < self.ylen : "y value out of range" + @require coord.x >= 0 && coord.x < self.xlen : "x value out of range" +*> +macro Type* Slice2d.get_coord_ref(self, usz[<2>] coord) +{ + return self.get_xy_ref(coord.x, coord.y); +} + +<* + Get the value at a particular x/y position in the slice. + + @param x : "The x coordinate" + @param y : "The x coordinate" + @return "The value at that coordinate" + @require y >= 0 && y < self.ylen : "y value out of range" + @require x >= 0 && x < self.xlen : "x value out of range" +*> +macro Type Slice2d.get_xy(self, x, y) +{ + return *self.get_xy_ref(x, y); +} + +<* + Get the value at a particular x/y position in the slice by reference. + + @param x : "The x coordinate" + @param y : "The y coordinate" + @return "A pointer to the value at that coordinate" + @require y >= 0 && y < self.ylen : "y value out of range" + @require x >= 0 && x < self.xlen : "x value out of range" +*> +macro Type* Slice2d.get_xy_ref(self, x, y) +{ + return self.ptr + self.inner_len * (y + self.ystart) + self.xstart + x; +} + +<* + Set the ´value at a particular x/y position in the slice. + + @param coord : "The xy coordinate" + @param value : "The new value" + @require coord.y >= 0 && coord.y < self.ylen : "y value out of range" + @require coord.x >= 0 && coord.x < self.xlen : "x value out of range" +*> +macro void Slice2d.set_coord(self, usz[<2>] coord, Type value) +{ + *self.get_coord_ref(coord) = value; +} + +<* + Set the value at a particular x/y position in the slice. + + @param x : "The x coordinate" + @param y : "The y coordinate" + @param value : "The new value" + @require y >= 0 && y < self.ylen : "y value out of range" + @require x >= 0 && x < self.xlen : "x value out of range" +*> +macro void Slice2d.set_xy(self, x, y, Type value) +{ + *self.get_xy_ref(x, y) = value; +} + +<* + Reslice a slice2d returning a new slice. + + @param x : "The starting x" + @param xlen : "The length along x" + @param y : "The starting y" + @param ylen : "The length along y" + @require y >= 0 && y < self.ylen + @require x >= 0 && x < self.xlen +*> +fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0) +{ + if (xlen < 1) xlen = self.xlen + xlen; + if (ylen < 1) ylen = self.ylen + ylen; + return { self.ptr, self.inner_len, y + self.ystart, ylen, x + self.xstart, xlen }; +} diff --git a/lib/std/math/bigint.c3 b/lib/std/math/bigint.c3 index 79a8629db..a08e5d4a4 100644 --- a/lib/std/math/bigint.c3 +++ b/lib/std/math/bigint.c3 @@ -127,13 +127,13 @@ fn bool BigInt.is_negative(&self) return self.data[MAX_LEN - 1] & 0x80000000 != 0; } -fn BigInt BigInt.add(self, BigInt other) +fn BigInt BigInt.add(self, BigInt other) @operator(+) { self.add_this(other); return self; } -fn void BigInt.add_this(&self, BigInt other) +fn void BigInt.add_this(&self, BigInt other) @operator(+=) { bool sign = self.is_negative(); bool sign_arg = other.is_negative(); @@ -178,7 +178,7 @@ fn BigInt BigInt.mult(self, BigInt bi2) @operator(*) return self; } -fn void BigInt.mult_this(&self, BigInt bi2) +fn void BigInt.mult_this(&self, BigInt bi2) @operator(*=) { if (bi2.is_zero()) { @@ -276,7 +276,7 @@ fn BigInt BigInt.sub(self, BigInt other) @operator(-) return self; } -fn BigInt* BigInt.sub_this(&self, BigInt other) +fn BigInt* BigInt.sub_this(&self, BigInt other) @operator(-=) { self.len = max(self.len, other.len); @@ -340,7 +340,7 @@ macro BigInt BigInt.div(self, BigInt other) @operator(/) return self; } -fn void BigInt.div_this(&self, BigInt other) +fn void BigInt.div_this(&self, BigInt other) @operator(/=) { bool negate_answer = self.is_negative(); @@ -383,7 +383,7 @@ fn BigInt BigInt.mod(self, BigInt bi2) @operator(%) return self; } -fn void BigInt.mod_this(&self, BigInt bi2) +fn void BigInt.mod_this(&self, BigInt bi2) @operator(%=) { if (bi2.is_negative()) { @@ -440,7 +440,7 @@ fn BigInt BigInt.shr(self, int shift) @operator(>>) return self; } -fn void BigInt.shr_this(self, int shift) +fn void BigInt.shr_this(self, int shift) @operator(>>=) { self.len = shift_right(&self.data, self.len, shift); } @@ -818,7 +818,7 @@ fn void BigInt.bit_xor_this(&self, BigInt bi2) self.reduce_len(); } -fn void BigInt.shl_this(&self, int shift) +fn void BigInt.shl_this(&self, int shift) @operator(<<=) { self.len = shift_left(&self.data, self.len, shift); } diff --git a/releasenotes.md b/releasenotes.md index 3378b874f..0e4939f26 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -7,9 +7,9 @@ - Better errors trying to convert an enum to an int and vice versa. - Function `@require` checks are added to the caller in safe mode. #186 - Improved error message when narrowing isn't allowed. -- Operator overloading for `+ - * / % & | ^ << >> ~ == !=` +- Operator overloading for `+ - * / % & | ^ << >> ~ == != += -= *= /= %= &= |= ^= <<= >>=` - Add `@operator_r` and `@operator_s` attributes. -- More stdlib tests: `sincos`. +- More stdlib tests: `sincos`, `ArenaAllocator`, `Slice2d`. ### Fixes - Trying to cast an enum to int and back caused the compiler to crash. diff --git a/src/compiler/enums.h b/src/compiler/enums.h index ca70fd2f9..30997a581 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -926,7 +926,17 @@ typedef enum OVERLOAD_SHR, OVERLOAD_EQUAL, OVERLOAD_NOT_EQUAL, - OVERLOADS_COUNT = OVERLOAD_NOT_EQUAL + OVERLOAD_PLUS_ASSIGN, + OVERLOAD_MINUS_ASSIGN, + OVERLOAD_MULTIPLY_ASSIGN, + OVERLOAD_DIVIDE_ASSIGN, + OVERLOAD_REMINDER_ASSIGN, + OVERLOAD_AND_ASSIGN, + OVERLOAD_OR_ASSIGN, + OVERLOAD_XOR_ASSIGN, + OVERLOAD_SHL_ASSIGN, + OVERLOAD_SHR_ASSIGN, + OVERLOADS_COUNT = OVERLOAD_SHR_ASSIGN } OperatorOverload; typedef enum diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index d6e7cb532..2c22dfabc 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -960,33 +960,63 @@ static Expr *parse_overload_from_token(ParseContext *c, TokenType token) case TOKEN_PLUS: overload = OVERLOAD_PLUS; break; + case TOKEN_PLUS_ASSIGN: + overload = OVERLOAD_PLUS_ASSIGN; + break; case TOKEN_MINUS: overload = OVERLOAD_MINUS; break; + case TOKEN_MINUS_ASSIGN: + overload = OVERLOAD_MINUS_ASSIGN; + break; case TOKEN_STAR: overload = OVERLOAD_MULTIPLY; break; + case TOKEN_MULT_ASSIGN: + overload = OVERLOAD_MULTIPLY_ASSIGN; + break; case TOKEN_DIV: overload = OVERLOAD_DIVIDE; break; + case TOKEN_DIV_ASSIGN: + overload = OVERLOAD_DIVIDE_ASSIGN; + break; case TOKEN_MOD: overload = OVERLOAD_REMINDER; break; + case TOKEN_MOD_ASSIGN: + overload = OVERLOAD_REMINDER_ASSIGN; + break; case TOKEN_AMP: overload = OVERLOAD_AND; break; + case TOKEN_BIT_AND_ASSIGN: + overload = OVERLOAD_AND_ASSIGN; + break; case TOKEN_BIT_OR: overload = OVERLOAD_OR; break; + case TOKEN_BIT_OR_ASSIGN: + overload = OVERLOAD_OR_ASSIGN; + break; case TOKEN_BIT_XOR: overload = OVERLOAD_XOR; break; + case TOKEN_BIT_XOR_ASSIGN: + overload = OVERLOAD_XOR_ASSIGN; + break; case TOKEN_SHL: overload = OVERLOAD_SHL; break; + case TOKEN_SHL_ASSIGN: + overload = OVERLOAD_SHL_ASSIGN; + break; case TOKEN_SHR: overload = OVERLOAD_SHR; break; + case TOKEN_SHR_ASSIGN: + overload = OVERLOAD_SHR_ASSIGN; + break; case TOKEN_BIT_NOT: overload = OVERLOAD_NEGATE; break; @@ -1081,6 +1111,16 @@ bool parse_attribute(ParseContext *c, Attr **attribute_ref, bool expect_eos) case TOKEN_SHR: case TOKEN_EQEQ: case TOKEN_NOT_EQUAL: + case TOKEN_BIT_AND_ASSIGN: + case TOKEN_BIT_OR_ASSIGN: + case TOKEN_BIT_XOR_ASSIGN: + case TOKEN_PLUS_ASSIGN: + case TOKEN_MINUS_ASSIGN: + case TOKEN_MULT_ASSIGN: + case TOKEN_DIV_ASSIGN: + case TOKEN_MOD_ASSIGN: + case TOKEN_SHL_ASSIGN: + case TOKEN_SHR_ASSIGN: if (!next_is_rparen) goto PARSE_EXPR; expr = parse_overload_from_token(c, c->tok); break; diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index 5c56ce95a..1ddcb8ae6 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -1953,7 +1953,7 @@ static inline bool sema_analyse_operator_arithmetics(SemaContext *context, Decl RETURN_SEMA_ERROR(rtype, "The return type was %s, but it must be bool for comparisons.", type_quoted_error_string(rtype->type)); } } - if (type_is_void(rtype->type)) + if (operator_overload < OVERLOAD_PLUS_ASSIGN && type_is_void(rtype->type)) { RETURN_SEMA_ERROR(rtype, "The return type may not be %s.", type_quoted_error_string(rtype->type)); } @@ -2034,6 +2034,16 @@ static bool sema_check_operator_method_validity(SemaContext *context, Decl *meth case OVERLOAD_XOR: case OVERLOAD_SHL: case OVERLOAD_SHR: + case OVERLOAD_PLUS_ASSIGN: + case OVERLOAD_MINUS_ASSIGN: + case OVERLOAD_MULTIPLY_ASSIGN: + case OVERLOAD_DIVIDE_ASSIGN: + case OVERLOAD_REMINDER_ASSIGN: + case OVERLOAD_AND_ASSIGN: + case OVERLOAD_OR_ASSIGN: + case OVERLOAD_XOR_ASSIGN: + case OVERLOAD_SHR_ASSIGN: + case OVERLOAD_SHL_ASSIGN: return sema_analyse_operator_arithmetics(context, method, operator); case OVERLOAD_NEGATE: return sema_analyse_operator_unary(context, method, operator); @@ -2117,6 +2127,53 @@ static inline void sema_get_overload_arguments(Decl *method, Type **value_ref, T } } +static const char *OVERLOAD_NAME[OVERLOADS_COUNT + 1] = +{ + [OVERLOAD_ELEMENT_AT] = "[]", + [OVERLOAD_ELEMENT_REF] = "&[]", + [OVERLOAD_ELEMENT_SET] = "[]=", + [OVERLOAD_LEN] = ".len", + [OVERLOAD_NEGATE] = "~", + [OVERLOAD_UNARY_MINUS] = "-", + [OVERLOAD_PLUS] = "+", + [OVERLOAD_MINUS] = "-", + [OVERLOAD_MULTIPLY] = "*", + [OVERLOAD_DIVIDE] = "/", + [OVERLOAD_REMINDER] = "%", + [OVERLOAD_AND] = "&", + [OVERLOAD_OR] = "|", + [OVERLOAD_XOR] = "^", + [OVERLOAD_SHL] = "<<", + [OVERLOAD_SHR] = ">>", + [OVERLOAD_EQUAL] = "==", + [OVERLOAD_NOT_EQUAL] = "!=", + [OVERLOAD_PLUS_ASSIGN] = "+=", + [OVERLOAD_MINUS_ASSIGN] = "-=", + [OVERLOAD_MULTIPLY_ASSIGN] = "*=", + [OVERLOAD_DIVIDE_ASSIGN] = "/=", + [OVERLOAD_REMINDER_ASSIGN] = "%=", + [OVERLOAD_AND_ASSIGN] = "&=", + [OVERLOAD_OR_ASSIGN] = "|=", + [OVERLOAD_XOR_ASSIGN] = "^=", + [OVERLOAD_SHL_ASSIGN] = "<<=", + [OVERLOAD_SHR_ASSIGN] = ">>=", +}; + +static bool OVERLOAD_MAY_BE_REVERSE[OVERLOADS_COUNT + 1] = +{ + [OVERLOAD_PLUS] = true, + [OVERLOAD_MINUS] = true, + [OVERLOAD_MULTIPLY] = true, + [OVERLOAD_DIVIDE] = true, + [OVERLOAD_REMINDER] = true, + [OVERLOAD_AND] = true, + [OVERLOAD_OR] = true, + [OVERLOAD_XOR] = true, + [OVERLOAD_SHL] = true, + [OVERLOAD_SHR] = true, + [OVERLOAD_EQUAL] = true, + [OVERLOAD_NOT_EQUAL] = true, +}; /** * Do checks on an operator method: * @@ -2138,20 +2195,7 @@ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type { RETURN_SEMA_ERROR(method, "Only user-defined types may have overloads."); } - bool is_symmetric = method->func_decl.overload_type == OVERLOAD_TYPE_SYMMETRIC; - bool is_reverse = method->func_decl.overload_type == OVERLOAD_TYPE_RIGHT; - - if (!second_param) - { - if (is_symmetric) RETURN_SEMA_ERROR(method, "Methods with single arguments cannot be have symmetric operators."); - if (is_reverse) RETURN_SEMA_ERROR(method, "Methods with single arguments cannot have reverse operators."); - } - else if (second_param->type_kind == parent_type->type_kind) - { - if (is_symmetric) RETURN_SEMA_ERROR(method, "Methods with same argument types cannot be have symmetric operators."); - if (is_reverse) RETURN_SEMA_ERROR(method, "Methods with the same argument types cannot have reverse operators."); - } - + Decl *other = NULL; if (operator >= OVERLOAD_TYPED_START) { @@ -2239,6 +2283,16 @@ INLINE bool sema_analyse_operator_method(SemaContext *context, Type *parent_type case OVERLOAD_MINUS: case OVERLOAD_EQUAL: case OVERLOAD_NOT_EQUAL: + case OVERLOAD_PLUS_ASSIGN: + case OVERLOAD_MINUS_ASSIGN: + case OVERLOAD_MULTIPLY_ASSIGN: + case OVERLOAD_DIVIDE_ASSIGN: + case OVERLOAD_REMINDER_ASSIGN: + case OVERLOAD_AND_ASSIGN: + case OVERLOAD_OR_ASSIGN: + case OVERLOAD_XOR_ASSIGN: + case OVERLOAD_SHL_ASSIGN: + case OVERLOAD_SHR_ASSIGN: return true; default: UNREACHABLE @@ -2951,9 +3005,17 @@ static bool sema_analyse_attribute(SemaContext *context, ResolvedAttrData *attr_ decl->func_decl.overload_type = OVERLOAD_TYPE_LEFT; break; case ATTRIBUTE_OPERATOR_R: + if (!OVERLOAD_MAY_BE_REVERSE[expr->overload_expr]) + { + RETURN_SEMA_ERROR(attr, "'%s' may not be used with @operator_r(), only @operator().", OVERLOAD_NAME[expr->overload_expr]); + } decl->func_decl.overload_type = OVERLOAD_TYPE_RIGHT; break; case ATTRIBUTE_OPERATOR_S: + if (!OVERLOAD_MAY_BE_REVERSE[expr->overload_expr]) + { + RETURN_SEMA_ERROR(attr, "'%s' may not be used with @operator_s(), only @operator().", OVERLOAD_NAME[expr->overload_expr]); + } decl->func_decl.overload_type = OVERLOAD_TYPE_SYMMETRIC; break; default: diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 8a589ff75..de5765eb1 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -208,6 +208,7 @@ static inline bool sema_analyse_expr_check(SemaContext *context, Expr *expr, Che static inline Expr **sema_prepare_splat_insert(Expr **exprs, unsigned added, unsigned insert_point); static inline bool sema_analyse_maybe_dead_expr(SemaContext *, Expr *expr, bool is_dead, Type *infer_type); static inline bool sema_insert_binary_overload(SemaContext *context, Expr *expr, Decl *overload, Expr *lhs, Expr *rhs, bool reverse); +static bool sema_replace_with_overload(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *left_type, OperatorOverload* operator_overload_ref); // -- implementations @@ -6212,6 +6213,34 @@ static bool sema_binary_analyse_ct_subscript_op_assign(SemaContext *context, Exp } +static BoolErr sema_insert_overload_in_op_assign_or_error(SemaContext *context, Expr *expr, Expr *left, Expr *right, BinaryOp operator, Type *lhs_type) +{ + if (type_is_user_defined(lhs_type)) + { + if (lhs_type->type_kind == TYPE_BITSTRUCT) + { + if (operator == BINARYOP_BIT_OR_ASSIGN || operator == BINARYOP_BIT_AND_ASSIGN || operator == BINARYOP_BIT_XOR_ASSIGN) return BOOL_FALSE; + } + if (!sema_analyse_inferred_expr(context, lhs_type, right)) return BOOL_ERR; + static OperatorOverload MAP[BINARYOP_LAST + 1] = { + [BINARYOP_ADD_ASSIGN] = OVERLOAD_PLUS_ASSIGN, + [BINARYOP_SUB_ASSIGN] = OVERLOAD_MINUS_ASSIGN, + [BINARYOP_MULT_ASSIGN] = OVERLOAD_MULTIPLY_ASSIGN, + [BINARYOP_DIV_ASSIGN] = OVERLOAD_DIVIDE_ASSIGN, + [BINARYOP_MOD_ASSIGN] = OVERLOAD_REMINDER_ASSIGN, + [BINARYOP_BIT_XOR_ASSIGN] = OVERLOAD_XOR_ASSIGN, + [BINARYOP_BIT_OR_ASSIGN] = OVERLOAD_OR_ASSIGN, + [BINARYOP_BIT_AND_ASSIGN] = OVERLOAD_AND_ASSIGN, + [BINARYOP_SHL_ASSIGN] = OVERLOAD_SHL_ASSIGN, + [BINARYOP_SHR_ASSIGN] = OVERLOAD_SHR_ASSIGN, + }; + OperatorOverload overload = MAP[operator]; + assert(overload && "Overload not mapped"); + if (!sema_replace_with_overload(context, expr, left, right, lhs_type, &overload)) return BOOL_ERR; + if (!overload) return BOOL_TRUE; + } + return BOOL_FALSE; +} /** * Analyse *= /= %= ^= |= &= += -= <<= >>= * @@ -6274,11 +6303,14 @@ static bool sema_expr_analyse_op_assign(SemaContext *context, Expr *expr, Expr * Type *no_fail = type_no_optional(left->type); Type *flat = type_flatten(no_fail); + BoolErr b = sema_insert_overload_in_op_assign_or_error(context, expr, left, right, operator, no_fail->canonical); + if (b == BOOL_ERR) return false; + if (b == BOOL_TRUE) return true; + // 3. If this is only defined for ints (^= |= &= %=) verify that this is an int. if (int_only && !type_flat_is_intlike(flat)) { if (is_bit_op && (flat->type_kind == TYPE_BITSTRUCT || flat == type_bool || type_flat_is_bool_vector(flat))) goto BITSTRUCT_OK; - RETURN_SEMA_ERROR(left, "Expected an integer here, not a value of type %s.", type_quoted_error_string(left->type)); } @@ -6421,6 +6453,7 @@ END: // 7. Assign type expr->type = type_add_optional(left->type, optional); return true; + } static bool sema_replace_with_overload(SemaContext *context, Expr *expr, Expr *left, Expr *right, Type *left_type, OperatorOverload* operator_overload_ref) diff --git a/test/unit/stdlib/collections/bitset.c3 b/test/unit/stdlib/collections/bitset.c3 index 25e06e7d5..0f9f95260 100644 --- a/test/unit/stdlib/collections/bitset.c3 +++ b/test/unit/stdlib/collections/bitset.c3 @@ -4,10 +4,62 @@ import std::collections::growablebitset; import std::collections::list; import std::io; -alias List = List{usz}; +alias List = List {usz}; -alias BitSet = BitSet{2048}; +alias BitSet = BitSet {2048}; +fn void bit_ops_assign() +{ + BitSet bs; + BitSet bs2; + bs.set(4); + bs.set(6); + bs2.set(4); + bs2.set(8); + BitSet bs3 = bs; + bs3 ^= bs2; + assert(!bs3.get(4)); + assert(!bs3.get(5)); + assert(bs3.get(6)); + assert(bs3.get(8)); + BitSet bs4 = bs; + bs4 |= bs2; + assert(bs4.get(4)); + assert(!bs4.get(5)); + assert(bs4.get(6)); + assert(bs4.get(8)); + BitSet bs5 = bs; + bs5 &= bs2; + assert(bs5.get(4)); + assert(!bs5.get(5)); + assert(!bs5.get(6)); + assert(!bs5.get(8)); +} + +fn void bit_ops() +{ + BitSet bs; + BitSet bs2; + bs.set(4); + bs.set(6); + bs2.set(4); + bs2.set(8); + BitSet bs3 = bs ^ bs2; + assert(!bs3.get(4)); + assert(!bs3.get(5)); + assert(bs3.get(6)); + assert(bs3.get(8)); + BitSet bs4 = bs | bs2; + assert(bs4.get(4)); + assert(!bs4.get(5)); + assert(bs4.get(6)); + assert(bs4.get(8)); + BitSet bs5 = bs & bs2; + assert(bs5.get(4)); + assert(!bs5.get(5)); + assert(!bs5.get(6)); + assert(!bs5.get(8)); +} fn void set_get() { BitSet bs; diff --git a/test/unit/stdlib/core/slice2d.c3 b/test/unit/stdlib/core/slice2d.c3 new file mode 100644 index 000000000..b4f498e15 --- /dev/null +++ b/test/unit/stdlib/core/slice2d.c3 @@ -0,0 +1,19 @@ +module test_slice @test; + +fn void slice2d() +{ + int[3][2] x = { { 1, 2, 3 }, { 4, 5, 6 }}; + Slice2d {int} s = array::slice2d(&x); + assert(s.len() == 2); + assert(s.count() == 6); + assert(s.get_xy(1, 1) == 5); + assert(s.get_coord({1, 1}) == 5); + s.set_coord({ 0, 1 }, 100); + assert(x[1][0] == 100); + s.set_xy(0, 1, 101); + assert(x[1][0] == 101); + Slice2d {int} s2 = s.slice(1, 2, 0, 2); + assert(s2[0] == { 2, 3 }); + assert(s2.count() == 4); + +} \ No newline at end of file diff --git a/test/unit/stdlib/mem/arena_allocator.c3 b/test/unit/stdlib/mem/arena_allocator.c3 new file mode 100644 index 000000000..dc0d3c619 --- /dev/null +++ b/test/unit/stdlib/mem/arena_allocator.c3 @@ -0,0 +1,19 @@ +module allocator_test @test; +import std::core::mem; + + +fn void test_arena_allocator_err() +{ + char[40] data; + char[40] empty; + ArenaAllocator* foo = allocator::wrap(&data); + char* alloc = allocator::malloc(foo, 5); + alloc[0] = 3; + assert(alloc >= &data[0] && alloc <= &data[^1]); + assert(foo.used >= 5); + assert(data != empty); + test::@error(allocator::malloc_try(foo, 50), mem::INVALID_ALLOC_SIZE); + test::@error(allocator::malloc_try(foo, 30), mem::OUT_OF_MEMORY); + foo.clear(); + (void)allocator::malloc(foo, 20); +} \ No newline at end of file