Added += and related as overloads. Updated tests and docs. Slice2 extracted to its own file.

This commit is contained in:
Christoffer Lerno
2025-04-14 00:55:46 +02:00
parent dca805bd8a
commit f85198e3ee
15 changed files with 608 additions and 143 deletions

View File

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

View File

@@ -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;
}
}
// Internal data
struct ArenaAllocatorHeader @local
{
usz size;
char[*] data;
}

View File

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

View File

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

View File

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

169
lib/std/core/slice2d.c3 Normal file
View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

@@ -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);
}