diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index fc286b95f..9247057ef 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -594,7 +594,7 @@ fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard /** * @require $vacount < 2 : "Too many arguments." - * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $vacount == 0 ||| $assignable($vaexpr(0), $Type) : "The second argument must be an initializer for the type" * @require $Type.alignof <= DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'alloc_aligned' instead" **/ macro new($Type, ...) @nodiscard @@ -612,7 +612,7 @@ macro new($Type, ...) @nodiscard * Allocate using an aligned allocation. This is necessary for types with a default memory alignment * exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. * @require $vacount < 2 : "Too many arguments." - * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $vacount == 0 ||| $assignable($vaexpr(0), $Type) : "The second argument must be an initializer for the type" **/ macro new_aligned($Type, ...) @nodiscard { @@ -644,7 +644,7 @@ macro alloc_aligned($Type) @nodiscard /** * @require $vacount < 2 : "Too many arguments." - * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $vacount == 0 ||| $assignable($vaexpr(0), $Type) : "The second argument must be an initializer for the type" **/ macro temp_new($Type, ...) @nodiscard { diff --git a/lib/std/core/mem_allocator.c3 b/lib/std/core/mem_allocator.c3 index 05e605eac..ec0499186 100644 --- a/lib/std/core/mem_allocator.c3 +++ b/lib/std/core/mem_allocator.c3 @@ -149,7 +149,7 @@ macro void free_aligned(Allocator allocator, void* ptr) /** * @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" * @require $vacount < 2 : "Too many arguments." - * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $vacount == 0 ||| $assignable($vaexpr(0), $Type) : "The second argument must be an initializer for the type" **/ macro new(Allocator allocator, $Type, ...) @nodiscard { @@ -165,7 +165,7 @@ macro new(Allocator allocator, $Type, ...) @nodiscard /** * @require $Type.alignof <= mem::DEFAULT_MEM_ALIGNMENT : "Types with alignment exceeding the default must use 'new_aligned' instead" * @require $vacount < 2 : "Too many arguments." - * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $vacount == 0 ||| $assignable($vaexpr(0), $Type) : "The second argument must be an initializer for the type" **/ macro new_try(Allocator allocator, $Type, ...) @nodiscard { @@ -182,7 +182,7 @@ macro new_try(Allocator allocator, $Type, ...) @nodiscard * Allocate using an aligned allocation. This is necessary for types with a default memory alignment * exceeding DEFAULT_MEM_ALIGNMENT. IMPORTANT! It must be freed using free_aligned. * @require $vacount < 2 : "Too many arguments." - * @require $or($vacount == 0, $assignable($vaexpr(0), $Type)) : "The second argument must be an initializer for the type" + * @require $vacount == 0 ||| $assignable($vaexpr(0), $Type) : "The second argument must be an initializer for the type" **/ macro new_aligned($Type, ...) @nodiscard { diff --git a/lib/std/core/values.c3 b/lib/std/core/values.c3 index 5498a1c7a..52e1bb145 100644 --- a/lib/std/core/values.c3 +++ b/lib/std/core/values.c3 @@ -30,7 +30,7 @@ macro promote_int_same(x, y) { $if @is_int(x): $switch - $case $and(@is_vector(y), $typeof(y).inner == float.typeid): + $case @is_vector(y) &&& $typeof(y).inner == float.typeid: return (float)x; $case $typeof(y).typeid == float.typeid: return (float)x; diff --git a/lib/std/libc/os/posix.c3 b/lib/std/libc/os/posix.c3 index dd26b74d4..896399d1d 100644 --- a/lib/std/libc/os/posix.c3 +++ b/lib/std/libc/os/posix.c3 @@ -54,7 +54,7 @@ struct Stack_t extern fn CInt sigaltstack(Stack_t* ss, Stack_t* old_ss); extern fn CInt sigaction(CInt signum, Sigaction *action, Sigaction *oldaction); -module libc::termios @if($and(env::LIBC, env::POSIX)); +module libc::termios @if(env::LIBC &&& env::POSIX); distinct Cc = char; distinct Speed = CUInt; diff --git a/lib/std/libc/termios.c3 b/lib/std/libc/termios.c3 index 124f4d66e..446b51364 100644 --- a/lib/std/libc/termios.c3 +++ b/lib/std/libc/termios.c3 @@ -1,4 +1,4 @@ -module libc::termios @if($and(env::LIBC, env::POSIX)); +module libc::termios @if(env::LIBC &&& env::POSIX); fn int sendBreak(Fd fd, int duration) => tcsendbreak(fd, duration); fn int drain(Fd fd) => tcdrain(fd); @@ -11,7 +11,7 @@ fn int Termios.setISpeed(Termios* self, Speed speed) => cfsetispeed(self, speed) fn int Termios.getAttr(Termios* self, Fd fd) => tcgetattr(fd, self); fn int Termios.setAttr(Termios* self, Fd fd, int optional_actions) => tcsetattr(fd, optional_actions, self); -module libc::termios @if($or(!env::LIBC, !env::POSIX)); +module libc::termios @if(!env::LIBC ||| !env::POSIX); distinct Cc = char; distinct Speed = CUInt; diff --git a/lib/std/sort/countingsort.c3 b/lib/std/sort/countingsort.c3 index 864cb9276..ed997541a 100644 --- a/lib/std/sort/countingsort.c3 +++ b/lib/std/sort/countingsort.c3 @@ -32,7 +32,7 @@ def Indexs = char[256] @private; def ElementType = $typeof(Type{}[0]); const bool NO_KEY_FN @private = types::is_same(KeyFn, EmptySlot); -const bool KEY_BY_VALUE @private = $or(NO_KEY_FN, $assignable(Type{}[0], $typefrom(KeyFn.params[0]))); +const bool KEY_BY_VALUE @private = NO_KEY_FN ||| $assignable(Type{}[0], $typefrom(KeyFn.params[0])); const bool LIST_HAS_REF @private = $defined(&Type{}[0]); def KeyFnReturnType = $typefrom(KeyFn.returns) @if(!NO_KEY_FN); diff --git a/lib/std/sort/insertionsort.c3 b/lib/std/sort/insertionsort.c3 index 03707f5d5..f3d535762 100644 --- a/lib/std/sort/insertionsort.c3 +++ b/lib/std/sort/insertionsort.c3 @@ -20,7 +20,7 @@ fn void isort(Type list, usz low, usz high, CmpFn comp, Context context) { var $has_cmp = @is_valid_macro_slot(comp); var $has_context = @is_valid_macro_slot(context); - var $cmp_by_value = $and($has_cmp, $assignable(list[0], $typefrom(CmpFn.params[0]))); + var $cmp_by_value = $has_cmp &&& $assignable(list[0], $typefrom(CmpFn.params[0])); var $has_get_ref = $defined(&list[0]); for (usz i = low; i < high; ++i) { diff --git a/lib/std/sort/quicksort.c3 b/lib/std/sort/quicksort.c3 index 4df35b421..bb6ac7398 100644 --- a/lib/std/sort/quicksort.c3 +++ b/lib/std/sort/quicksort.c3 @@ -31,7 +31,8 @@ fn void qsort(Type list, isz low, isz high, CmpFn cmp, Context context) { var $has_cmp = @is_valid_macro_slot(cmp); var $has_context = @is_valid_macro_slot(context); - var $cmp_by_value = $and($has_cmp, $assignable(list[0], $typefrom(CmpFn.params[0]))); + var $cmp_by_value = $has_cmp &&& $assignable(list[0], $typefrom(CmpFn.params[0])); + if (low >= 0 && high >= 0 && low < high) { Stack stack; diff --git a/lib/std/sort/sort.c3 b/lib/std/sort/sort.c3 index 526c093bc..b65ae78db 100644 --- a/lib/std/sort/sort.c3 +++ b/lib/std/sort/sort.c3 @@ -20,7 +20,7 @@ macro bool @is_sortable(#list) return false; $default: return true; - $endswitch; + $endswitch } macro bool @is_valid_context(#cmp, #context) @@ -34,7 +34,7 @@ macro bool @is_valid_cmp_fn(#cmp, #list, #context) var $no_context = @is_empty_macro_slot(#context); $switch $case @is_empty_macro_slot(#cmp): return true; - $case $or($Type.kindof != FUNC, $Type.returns.kindof != SIGNED_INT): return false; + $case $Type.kindof != FUNC ||| $Type.returns.kindof != SIGNED_INT: return false; $case $defined(#cmp(#list[0], #list[0], #context)): return true; $case $defined(#cmp(#list[0], #list[0])): return $no_context; $case $defined(#cmp(&#list[0], &#list[0], #context)): return true; diff --git a/releasenotes.md b/releasenotes.md index fa60eb8ba..7debb7adf 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -23,6 +23,7 @@ - Add a `--run-once` option to delete the output file after running it. - Add `@const` attribute for macros, for better error messages with constant macros. - Add `wincrt` setting to libraries. +- Add `+++` `&&&` `|||` as replacement for `$concat`, `$and` and `$or`. ### Fixes @@ -38,6 +39,8 @@ - Distinct inline would not implement protocol if the inlined implemented it. #1292 - Distinct inline can now be called if it is aliasing a function pointer. - Bug in List add_array when reserving memory. +- Fix issue where a compile time parameter is followed by "...". +- Fix issue with some conversions to untyped list. ### Stdlib changes diff --git a/src/compiler/ast.c b/src/compiler/ast.c index 7a69225db..ac8cf52a6 100644 --- a/src/compiler/ast.c +++ b/src/compiler/ast.c @@ -192,6 +192,9 @@ BinaryOp binary_op[TOKEN_LAST + 1] = { [TOKEN_SHR] = BINARYOP_SHR, [TOKEN_AND] = BINARYOP_AND, [TOKEN_OR] = BINARYOP_OR, + [TOKEN_CT_AND] = BINARYOP_CT_AND, + [TOKEN_CT_OR] = BINARYOP_CT_OR, + [TOKEN_CT_CONCAT] = BINARYOP_CT_CONCAT, [TOKEN_QUESTQUEST] = BINARYOP_ELSE, [TOKEN_AMP] = BINARYOP_BIT_AND, [TOKEN_BIT_OR] = BINARYOP_BIT_OR, diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index b81224637..cc0eea76f 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2317,6 +2317,7 @@ void expr_rewrite_to_binary(Expr *expr_to_rewrite, Expr *left, Expr *right, Bina bool expr_const_in_range(const ExprConst *left, const ExprConst *right, const ExprConst *right_to); bool expr_const_compare(const ExprConst *left, const ExprConst *right, BinaryOp op); +void expr_contract_array(ExprConst *expr_const, ConstKind contract_type); bool expr_const_will_overflow(const ExprConst *expr, TypeKind kind); const char *expr_const_to_error_string(const ExprConst *expr); bool expr_const_float_fits_type(const ExprConst *expr_const, TypeKind kind); diff --git a/src/compiler/enums.h b/src/compiler/enums.h index 99cca3fff..6f3277367 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -34,6 +34,9 @@ typedef enum BINARYOP_AND, BINARYOP_OR, BINARYOP_ELSE, + BINARYOP_CT_AND, + BINARYOP_CT_OR, + BINARYOP_CT_CONCAT, // Don't change the ordering for GT to EQ or things will break BINARYOP_GT, BINARYOP_GE, @@ -487,7 +490,9 @@ typedef enum TOKEN_ELLIPSIS, // ... TOKEN_SHL_ASSIGN, // <<= TOKEN_SHR_ASSIGN, // >>= - + TOKEN_CT_AND, // &&& + TOKEN_CT_CONCAT, // +++ + TOKEN_CT_OR, // ||| // Literals. TOKEN_IDENT, // Any normal ident. TOKEN_CONST_IDENT, // Any purely uppercase ident, @@ -587,12 +592,12 @@ typedef enum TOKEN_LAST_NON_CT_KEYWORD = TOKEN_WHILE, TOKEN_CT_ALIGNOF, // $alignof - TOKEN_CT_AND, // $and + TOKEN_CT_ANDFN, // $and TOKEN_CT_APPEND, // $append TOKEN_CT_ASSERT, // $assert TOKEN_CT_ASSIGNABLE, // $assignable TOKEN_CT_CASE, // $case - TOKEN_CT_CONCAT, // $concat + TOKEN_CT_CONCATFN, // $concat TOKEN_CT_DEFAULT, // $default TOKEN_CT_DEFINED, // $defined TOKEN_CT_ECHO, // $echo @@ -615,7 +620,7 @@ typedef enum TOKEN_CT_IS_CONST, // $is_const TOKEN_CT_NAMEOF, // $nameof TOKEN_CT_OFFSETOF, // $offsetof - TOKEN_CT_OR, // $or + TOKEN_CT_ORFN, // $or TOKEN_CT_QNAMEOF, // $qnameof TOKEN_CT_SIZEOF, // $sizeof TOKEN_CT_STRINGIFY, // $stringify @@ -699,6 +704,7 @@ typedef enum TYPE_BITSTRUCT, TYPE_FAULTTYPE, TYPE_TYPEDEF, + TYPE_UNTYPED_LIST, TYPE_SLICE, TYPE_ARRAY, TYPE_FIRST_ARRAYLIKE = TYPE_ARRAY, @@ -707,7 +713,6 @@ typedef enum TYPE_VECTOR, TYPE_INFERRED_VECTOR, TYPE_LAST_ARRAYLIKE = TYPE_INFERRED_VECTOR, - TYPE_UNTYPED_LIST, TYPE_OPTIONAL, TYPE_WILDCARD, TYPE_TYPEINFO, diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index db80addb8..587e987f8 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -179,7 +179,7 @@ static bool add_error_token_at_current(Lexer *lexer, const char *message, ...) } // Add a new regular token. -static inline bool return_token(Lexer *lexer, TokenType type, const char *string) +static inline bool new_token(Lexer *lexer, TokenType type, const char *string) { set_generic_token(lexer, type); lexer->data.string = string; @@ -341,7 +341,7 @@ static inline bool scan_ident(Lexer *lexer, TokenType normal, TokenType const_to uint32_t len = (uint32_t)(lexer->current - lexer->lexing_start); if (!type) { - if (!prefix && len == 1) return return_token(lexer, TOKEN_UNDERSCORE, "_"); + if (!prefix && len == 1) return new_token(lexer, TOKEN_UNDERSCORE, "_"); if (prefix && len == 1) { return add_error_token(lexer, "An identifier was expected after the '%c'.", prefix); @@ -357,7 +357,7 @@ static inline bool scan_ident(Lexer *lexer, TokenType normal, TokenType const_to default: break; } - return return_token(lexer, type, interned_string); + return new_token(lexer, type, interned_string); } // --- Number scanning @@ -446,7 +446,7 @@ static bool scan_oct(Lexer *lexer) { return add_error_token(lexer, "Octal literals cannot have a floating point suffix."); } - return return_token(lexer, TOKEN_INTEGER, lexer->lexing_start); + return new_token(lexer, TOKEN_INTEGER, lexer->lexing_start); } /** @@ -470,7 +470,7 @@ static bool scan_binary(Lexer *lexer) { return add_error_token(lexer, "Binary literals cannot have a floating point suffix."); } - return return_token(lexer, TOKEN_INTEGER, lexer->lexing_start); + return new_token(lexer, TOKEN_INTEGER, lexer->lexing_start); } /** @@ -541,7 +541,7 @@ static inline bool scan_hex(Lexer *lexer) return add_error_token_at_current(lexer, "The number ended with '_', which isn't allowed, please remove it."); } if (!scan_number_suffix(lexer, &is_float)) return false; - return return_token(lexer, is_float ? TOKEN_REAL : TOKEN_INTEGER, lexer->lexing_start); + return new_token(lexer, is_float ? TOKEN_REAL : TOKEN_INTEGER, lexer->lexing_start); } /** @@ -588,7 +588,7 @@ static inline bool scan_dec(Lexer *lexer) return add_error_token_at_current(lexer, "The number ended with '_', which isn't allowed, please remove it."); } if (!scan_number_suffix(lexer, &is_float)) return false; - return return_token(lexer, is_float ? TOKEN_REAL : TOKEN_INTEGER, lexer->lexing_start); + return new_token(lexer, is_float ? TOKEN_REAL : TOKEN_INTEGER, lexer->lexing_start); } /** @@ -989,7 +989,7 @@ static inline bool scan_string(Lexer *lexer) // Skip the `"` next(lexer); destination[len] = 0; - return_token(lexer, TOKEN_STRING, destination); + new_token(lexer, TOKEN_STRING, destination); lexer->data.strlen = len; return true; } @@ -1025,7 +1025,7 @@ static inline bool scan_raw_string(Lexer *lexer) destination[len++] = c; } destination[len] = 0; - return_token(lexer, TOKEN_STRING, destination); + new_token(lexer, TOKEN_STRING, destination); lexer->data.strlen = len; return true; } @@ -1069,7 +1069,7 @@ static inline bool scan_hex_array(Lexer *lexer) { return add_error_token(lexer, "The hexadecimal string is not an even length, did you miss a digit somewhere?"); } - if (!return_token(lexer, TOKEN_BYTES, lexer->lexing_start)) return false; + if (!new_token(lexer, TOKEN_BYTES, lexer->lexing_start)) return false; lexer->data.is_base64 = false; lexer->data.bytes_len = (uint64_t)len / 2; return true; @@ -1149,7 +1149,7 @@ static inline bool scan_base64(Lexer *lexer) "- only need 1 or 2 bytes of extra padding."); } uint64_t decoded_len = (3 * len - end_len) / 4; - if (!return_token(lexer, TOKEN_BYTES, lexer->lexing_start)) return false; + if (!new_token(lexer, TOKEN_BYTES, lexer->lexing_start)) return false; lexer->data.is_base64 = true; lexer->data.bytes_len = decoded_len; return true; @@ -1215,7 +1215,7 @@ RETRY:; { lexer->mode = LEX_NORMAL; next(lexer); - return return_token(lexer, TOKEN_DOCS_END, "*/"); + return new_token(lexer, TOKEN_DOCS_END, "*/"); } // We need to skip any space afterwards @@ -1240,7 +1240,7 @@ RETRY:; { next(lexer); lexer->mode = LEX_NORMAL; - return return_token(lexer, TOKEN_DOCS_END, "*/"); + return new_token(lexer, TOKEN_DOCS_END, "*/"); } // If we find the end of the line we start from the beginning. if (c == '\n') @@ -1263,7 +1263,7 @@ EOF_REACHED: static bool parse_doc_start(Lexer *lexer) { // Add the doc start token. - return_token(lexer, TOKEN_DOCS_START, lexer->lexing_start); + new_token(lexer, TOKEN_DOCS_START, lexer->lexing_start); skip_to_doc_line_end(lexer); lexer->mode = LEX_DOCS; return true; @@ -1277,7 +1277,7 @@ static bool lexer_scan_token_inner(Lexer *lexer) // Point start to the first non-whitespace character. begin_new_token(lexer); - if (reached_end(lexer)) return return_token(lexer, TOKEN_EOF, "\n") && false; + if (reached_end(lexer)) return new_token(lexer, TOKEN_EOF, "\n") && false; char c = peek(lexer); next(lexer); @@ -1290,7 +1290,7 @@ static bool lexer_scan_token_inner(Lexer *lexer) { return scan_ident(lexer, TOKEN_AT_IDENT, TOKEN_AT_CONST_IDENT, TOKEN_AT_TYPE_IDENT, '@'); } - return return_token(lexer, TOKEN_AT, "@"); + return new_token(lexer, TOKEN_AT, "@"); case '\'': return scan_char(lexer); case '`': @@ -1304,42 +1304,42 @@ static bool lexer_scan_token_inner(Lexer *lexer) { if (char_is_letter(peek(lexer))) { - return return_token(lexer, TOKEN_BUILTIN, "$$"); + return new_token(lexer, TOKEN_BUILTIN, "$$"); } return add_error_token_at_current(lexer, "Expected a letter after $$."); } return scan_ident(lexer, TOKEN_CT_IDENT, TOKEN_CT_CONST_IDENT, TOKEN_CT_TYPE_IDENT, '$'); case ',': - return return_token(lexer, TOKEN_COMMA, ","); + return new_token(lexer, TOKEN_COMMA, ","); case ';': - return return_token(lexer, TOKEN_EOS, ";"); + return new_token(lexer, TOKEN_EOS, ";"); case '{': - return match(lexer, '|') ? return_token(lexer, TOKEN_LBRAPIPE, "{|") : return_token(lexer, TOKEN_LBRACE, "{"); + return match(lexer, '|') ? new_token(lexer, TOKEN_LBRAPIPE, "{|") : new_token(lexer, TOKEN_LBRACE, "{"); case '}': - return return_token(lexer, TOKEN_RBRACE, "}"); + return new_token(lexer, TOKEN_RBRACE, "}"); case '(': - return match(lexer, '<') ? return_token(lexer, TOKEN_LGENPAR, "(<") : return_token(lexer, TOKEN_LPAREN, "("); + return match(lexer, '<') ? new_token(lexer, TOKEN_LGENPAR, "(<") : new_token(lexer, TOKEN_LPAREN, "("); case ')': - return return_token(lexer, TOKEN_RPAREN, ")"); + return new_token(lexer, TOKEN_RPAREN, ")"); case '[': - if (match(lexer, '<')) return return_token(lexer, TOKEN_LVEC, "[<"); - return return_token(lexer, TOKEN_LBRACKET, "["); + if (match(lexer, '<')) return new_token(lexer, TOKEN_LVEC, "[<"); + return new_token(lexer, TOKEN_LBRACKET, "["); case ']': - return return_token(lexer, TOKEN_RBRACKET, "]"); + return new_token(lexer, TOKEN_RBRACKET, "]"); case '.': if (match(lexer, '.')) { - if (match(lexer, '.')) return return_token(lexer, TOKEN_ELLIPSIS, "..."); - return return_token(lexer, TOKEN_DOTDOT, ".."); + if (match(lexer, '.')) return new_token(lexer, TOKEN_ELLIPSIS, "..."); + return new_token(lexer, TOKEN_DOTDOT, ".."); } - return return_token(lexer, TOKEN_DOT, "."); + return new_token(lexer, TOKEN_DOT, "."); case '~': - return return_token(lexer, TOKEN_BIT_NOT, "~"); + return new_token(lexer, TOKEN_BIT_NOT, "~"); case ':': - return match(lexer, ':') ? return_token(lexer, TOKEN_SCOPE, "::") : return_token(lexer, TOKEN_COLON, ":"); + return match(lexer, ':') ? new_token(lexer, TOKEN_SCOPE, "::") : new_token(lexer, TOKEN_COLON, ":"); case '!': - if (match(lexer, '!')) return return_token(lexer, TOKEN_BANGBANG, "!!"); - return match(lexer, '=') ? return_token(lexer, TOKEN_NOT_EQUAL, "!=") : return_token(lexer, TOKEN_BANG, "!"); + if (match(lexer, '!')) return new_token(lexer, TOKEN_BANGBANG, "!!"); + return match(lexer, '=') ? new_token(lexer, TOKEN_NOT_EQUAL, "!=") : new_token(lexer, TOKEN_BANG, "!"); case '/': // We can't get any directives comments here. if (lexer->mode != LEX_DOCS && match(lexer, '*')) @@ -1348,59 +1348,62 @@ static bool lexer_scan_token_inner(Lexer *lexer) next(lexer); return parse_doc_start(lexer); } - return match(lexer, '=') ? return_token(lexer, TOKEN_DIV_ASSIGN, "/=") : return_token(lexer, TOKEN_DIV, "/"); + return match(lexer, '=') ? new_token(lexer, TOKEN_DIV_ASSIGN, "/=") : new_token(lexer, TOKEN_DIV, "/"); case '*': - return match(lexer, '=') ? return_token(lexer, TOKEN_MULT_ASSIGN, "*=") : return_token(lexer, TOKEN_STAR, "*"); + return match(lexer, '=') ? new_token(lexer, TOKEN_MULT_ASSIGN, "*=") : new_token(lexer, TOKEN_STAR, "*"); case '=': - if (match(lexer, '>')) return return_token(lexer, TOKEN_IMPLIES, "=>"); - return match(lexer, '=') ? return_token(lexer, TOKEN_EQEQ, "==") : return_token(lexer, TOKEN_EQ, "="); + if (match(lexer, '>')) return new_token(lexer, TOKEN_IMPLIES, "=>"); + return match(lexer, '=') ? new_token(lexer, TOKEN_EQEQ, "==") : new_token(lexer, TOKEN_EQ, "="); case '^': - return match(lexer, '=') ? return_token(lexer, TOKEN_BIT_XOR_ASSIGN, "^=") : return_token(lexer, - TOKEN_BIT_XOR, - "^"); + return match(lexer, '=') ? new_token(lexer, TOKEN_BIT_XOR_ASSIGN, "^=") : new_token(lexer, TOKEN_BIT_XOR, "^"); case '?': - if (match(lexer, '?')) return return_token(lexer, TOKEN_QUESTQUEST, "??"); - return match(lexer, ':') ? return_token(lexer, TOKEN_ELVIS, "?:") : return_token(lexer, TOKEN_QUESTION, "?"); + if (match(lexer, '?')) return new_token(lexer, TOKEN_QUESTQUEST, "??"); + return match(lexer, ':') ? new_token(lexer, TOKEN_ELVIS, "?:") : new_token(lexer, TOKEN_QUESTION, "?"); case '<': if (match(lexer, '<')) { - if (match(lexer, '=')) return return_token(lexer, TOKEN_SHL_ASSIGN, "<<="); - return return_token(lexer, TOKEN_SHL, "<<"); + if (match(lexer, '=')) return new_token(lexer, TOKEN_SHL_ASSIGN, "<<="); + return new_token(lexer, TOKEN_SHL, "<<"); } - return match(lexer, '=') ? return_token(lexer, TOKEN_LESS_EQ, "<=") : return_token(lexer, TOKEN_LESS, "<"); + return match(lexer, '=') ? new_token(lexer, TOKEN_LESS_EQ, "<=") : new_token(lexer, TOKEN_LESS, "<"); case '>': if (match(lexer, '>')) { - if (match(lexer, '=')) return return_token(lexer, TOKEN_SHR_ASSIGN, ">>="); - return return_token(lexer, TOKEN_SHR, ">>"); + if (match(lexer, '=')) return new_token(lexer, TOKEN_SHR_ASSIGN, ">>="); + return new_token(lexer, TOKEN_SHR, ">>"); } - if (match(lexer, ')')) return return_token(lexer, TOKEN_RGENPAR, ">)"); - if (match(lexer, ']')) return return_token(lexer, TOKEN_RVEC, ">]"); - return match(lexer, '=') ? return_token(lexer, TOKEN_GREATER_EQ, ">=") : return_token(lexer, - TOKEN_GREATER, - ">"); + if (match(lexer, ')')) return new_token(lexer, TOKEN_RGENPAR, ">)"); + if (match(lexer, ']')) return new_token(lexer, TOKEN_RVEC, ">]"); + return match(lexer, '=') ? new_token(lexer, TOKEN_GREATER_EQ, ">=") : new_token(lexer, TOKEN_GREATER, ">"); case '%': - return match(lexer, '=') ? return_token(lexer, TOKEN_MOD_ASSIGN, "%=") : return_token(lexer, TOKEN_MOD, "%"); + return match(lexer, '=') ? new_token(lexer, TOKEN_MOD_ASSIGN, "%=") : new_token(lexer, TOKEN_MOD, "%"); case '&': - if (match(lexer, '&')) return return_token(lexer, TOKEN_AND, "&&"); - return match(lexer, '=') ? return_token(lexer, TOKEN_BIT_AND_ASSIGN, "&=") : return_token(lexer, - TOKEN_AMP, - "&"); + if (match(lexer, '&')) + { + return match(lexer, '&') ? new_token(lexer, TOKEN_CT_AND, "&&&") : new_token(lexer, TOKEN_AND, "&&"); + } + return match(lexer, '=') ? new_token(lexer, TOKEN_BIT_AND_ASSIGN, "&=") : new_token(lexer, TOKEN_AMP, "&"); case '|': - if (match(lexer, '}')) return return_token(lexer, TOKEN_RBRAPIPE, "|}"); - if (match(lexer, '|')) return return_token(lexer, TOKEN_OR, "||"); - return match(lexer, '=') ? return_token(lexer, TOKEN_BIT_OR_ASSIGN, "|=") : return_token(lexer, - TOKEN_BIT_OR, - "|"); + if (match(lexer, '}')) return new_token(lexer, TOKEN_RBRAPIPE, "|}"); + if (match(lexer, '|')) + { + return match(lexer, '|') ? new_token(lexer, TOKEN_CT_OR, "|||") : new_token(lexer, TOKEN_OR, "||"); + } + return match(lexer, '=') ? new_token(lexer, TOKEN_BIT_OR_ASSIGN, "|=") : new_token(lexer, TOKEN_BIT_OR, + "|"); case '+': - if (match(lexer, '+')) return return_token(lexer, TOKEN_PLUSPLUS, "++"); - if (match(lexer, '=')) return return_token(lexer, TOKEN_PLUS_ASSIGN, "+="); - return return_token(lexer, TOKEN_PLUS, "+"); + if (match(lexer, '+')) + { + if (match(lexer, '+')) return new_token(lexer, TOKEN_CT_CONCAT, "+++"); + return new_token(lexer, TOKEN_PLUSPLUS, "++"); + } + if (match(lexer, '=')) return new_token(lexer, TOKEN_PLUS_ASSIGN, "+="); + return new_token(lexer, TOKEN_PLUS, "+"); case '-': - if (match(lexer, '>')) return return_token(lexer, TOKEN_ARROW, "->"); - if (match(lexer, '-')) return return_token(lexer, TOKEN_MINUSMINUS, "--"); - if (match(lexer, '=')) return return_token(lexer, TOKEN_MINUS_ASSIGN, "-="); - return return_token(lexer, TOKEN_MINUS, "-"); + if (match(lexer, '>')) return new_token(lexer, TOKEN_ARROW, "->"); + if (match(lexer, '-')) return new_token(lexer, TOKEN_MINUSMINUS, "--"); + if (match(lexer, '=')) return new_token(lexer, TOKEN_MINUS_ASSIGN, "-="); + return new_token(lexer, TOKEN_MINUS, "-"); case 'x': if ((peek(lexer) == '"' || peek(lexer) == '\'')) { diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 257caac5b..f49505013 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -4408,6 +4408,9 @@ void llvm_emit_binary(GenContext *c, BEValue *be_value, Expr *expr, BEValue *lhs case BINARYOP_BIT_XOR_ASSIGN: case BINARYOP_SHR_ASSIGN: case BINARYOP_SHL_ASSIGN: + case BINARYOP_CT_AND: + case BINARYOP_CT_OR: + case BINARYOP_CT_CONCAT: // Handled elsewhere. UNREACHABLE } diff --git a/src/compiler/number.c b/src/compiler/number.c index 283dd7b3d..4b87df189 100644 --- a/src/compiler/number.c +++ b/src/compiler/number.c @@ -49,11 +49,87 @@ static inline bool compare_fps(Real left, Real right, BinaryOp op) UNREACHABLE } } +void expr_contract_array(ExprConst *expr_const, ConstKind contract_type) +{ + if (expr_const->const_kind == CONST_POINTER) + { + *expr_const = (ExprConst) { .const_kind = contract_type }; + return; + } + assert(expr_const->const_kind == CONST_INITIALIZER); + Type *type = type_flatten(expr_const->initializer->type); + assert(type_is_any_arraylike(type)); + ArraySize len = type->array.len; + if (!len) + { + *expr_const = (ExprConst) { .const_kind = contract_type }; + return; + } + char *arr = calloc_arena(len); + ConstInitializer *initializer = expr_const->initializer; + switch (initializer->kind) + { + case CONST_INIT_ZERO: + break; + case CONST_INIT_STRUCT: + case CONST_INIT_UNION: + case CONST_INIT_VALUE: + case CONST_INIT_ARRAY_VALUE: + UNREACHABLE + case CONST_INIT_ARRAY: + { + FOREACH(ConstInitializer *, init, initializer->init_array.elements) + { + assert(init->kind == CONST_INIT_ARRAY_VALUE); + arr[init->init_array_value.index] = (char) int_to_i64(init->init_array_value.element->init_value->const_expr.ixx); + } + break; + } + case CONST_INIT_ARRAY_FULL: + { + FOREACH_IDX(i, ConstInitializer *, init, initializer->init_array_full) + { + assert(init->kind == CONST_INIT_VALUE); + arr[i] = (char)int_to_i64(init->init_value->const_expr.ixx); + } + break; + } + } + *expr_const = (ExprConst) { .const_kind = contract_type, .bytes.ptr = arr, .bytes.len = len }; +} + +INLINE bool const_is_bytes(ConstKind kind) +{ + return kind == CONST_BYTES || kind == CONST_STRING; +} bool expr_const_compare(const ExprConst *left, const ExprConst *right, BinaryOp op) { bool is_eq; - assert(left->const_kind == right->const_kind); + ConstKind left_kind = left->const_kind; + ConstKind right_kind = right->const_kind; + ExprConst replace; + if (left_kind != right_kind) + { + if (const_is_bytes(left_kind)) + { + if (!const_is_bytes(right_kind)) + { + replace = *right; + expr_contract_array(&replace, left_kind); + right = &replace; + } + } + else if (const_is_bytes(right_kind)) + { + if (!const_is_bytes(left_kind)) + { + replace = *left; + expr_contract_array(&replace, right_kind); + left = &replace; + } + } + } switch (left->const_kind) { case CONST_BOOL: diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index dee6e8e1d..067ada9db 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -1135,7 +1135,7 @@ static Expr *parse_ct_embed(ParseContext *c, Expr *left) static Expr *parse_ct_concat_append(ParseContext *c, Expr *left) { assert(!left && "Unexpected left hand side"); - Expr *expr = EXPR_NEW_TOKEN(tok_is(c, TOKEN_CT_CONCAT) ? EXPR_CT_CONCAT : EXPR_CT_APPEND); + Expr *expr = EXPR_NEW_TOKEN(tok_is(c, TOKEN_CT_CONCATFN) ? EXPR_CT_CONCAT : EXPR_CT_APPEND); advance(c); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_expr); @@ -1170,7 +1170,7 @@ static Expr *parse_ct_and_or(ParseContext *c, Expr *left) { assert(!left && "Unexpected left hand side"); Expr *expr = EXPR_NEW_TOKEN(EXPR_CT_AND_OR); - expr->ct_and_or_expr.is_and = tok_is(c, TOKEN_CT_AND); + expr->ct_and_or_expr.is_and = tok_is(c, TOKEN_CT_ANDFN); advance(c); CONSUME_OR_RET(TOKEN_LPAREN, poisoned_expr); if (!parse_expr_list(c, &expr->ct_and_or_expr.args, TOKEN_RPAREN)) return poisoned_expr; @@ -1956,7 +1956,10 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_STRING] = { parse_string_literal, NULL, PREC_NONE }, [TOKEN_REAL] = { parse_double, NULL, PREC_NONE }, [TOKEN_OR] = { NULL, parse_binary, PREC_OR }, + [TOKEN_CT_OR] = { NULL, parse_binary, PREC_OR }, + [TOKEN_CT_CONCAT] = { NULL, parse_binary, PREC_MULTIPLICATIVE }, [TOKEN_AND] = { parse_unary_expr, parse_binary, PREC_AND }, + [TOKEN_CT_AND] = { parse_unary_expr, parse_binary, PREC_AND }, [TOKEN_EQ] = { NULL, parse_binary, PREC_ASSIGNMENT }, [TOKEN_PLUS_ASSIGN] = { NULL, parse_binary, PREC_ASSIGNMENT }, [TOKEN_MINUS_ASSIGN] = { NULL, parse_binary, PREC_ASSIGNMENT }, @@ -1980,11 +1983,11 @@ ParseRule rules[TOKEN_EOF + 1] = { //[TOKEN_HASH_TYPE_IDENT] = { parse_type_identifier, NULL, PREC_NONE } [TOKEN_FN] = { parse_lambda, NULL, PREC_NONE }, - [TOKEN_CT_CONCAT] = { parse_ct_concat_append, NULL, PREC_NONE }, + [TOKEN_CT_CONCATFN] = {parse_ct_concat_append, NULL, PREC_NONE }, [TOKEN_CT_APPEND] = { parse_ct_concat_append, NULL, PREC_NONE }, [TOKEN_CT_SIZEOF] = { parse_ct_sizeof, NULL, PREC_NONE }, [TOKEN_CT_ALIGNOF] = { parse_ct_call, NULL, PREC_NONE }, - [TOKEN_CT_AND] = {parse_ct_and_or, NULL, PREC_NONE }, + [TOKEN_CT_ANDFN] = {parse_ct_and_or, NULL, PREC_NONE }, [TOKEN_CT_ASSIGNABLE] = { parse_ct_castable, NULL, PREC_NONE }, [TOKEN_CT_DEFINED] = { parse_ct_defined, NULL, PREC_NONE }, [TOKEN_CT_IS_CONST] = {parse_ct_is_const, NULL, PREC_NONE }, @@ -1993,7 +1996,7 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_CT_FEATURE] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_EXTNAMEOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_OFFSETOF] = { parse_ct_call, NULL, PREC_NONE }, - [TOKEN_CT_OR] = {parse_ct_and_or, NULL, PREC_NONE }, + [TOKEN_CT_ORFN] = {parse_ct_and_or, NULL, PREC_NONE }, [TOKEN_CT_NAMEOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_QNAMEOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_TYPEFROM] = { parse_type_expr, NULL, PREC_NONE }, diff --git a/src/compiler/parse_global.c b/src/compiler/parse_global.c index 0151a7699..914139caf 100644 --- a/src/compiler/parse_global.c +++ b/src/compiler/parse_global.c @@ -1273,7 +1273,7 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Decl **body_params, // Did we get Foo... foo... if (ellipsis) { - PRINT_ERROR_HERE("Unexpected '...' following a vararg declaration."); + PRINT_ERROR_LAST("Unexpected '...' following a vararg declaration."); return false; } ellipsis = true; @@ -1285,7 +1285,7 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Decl **body_params, // Did we get Foo foo...? If so then that's an error. if (type) { - PRINT_ERROR_HERE("For typed varargs '...', needs to appear after the type."); + PRINT_ERROR_LAST("For typed varargs '...', needs to appear after the type."); return false; } // This is "foo..." @@ -1299,9 +1299,9 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Decl **body_params, name = symstr(c); advance_and_verify(c, TOKEN_CT_IDENT); // This will catch Type... $foo and $foo..., neither is allowed. - if (ellipsis || peek(c) == TOKEN_ELLIPSIS) + if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) { - PRINT_ERROR_HERE("Compile time parameters may not be varargs, use untyped macro varargs '...' instead."); + PRINT_ERROR_LAST("Compile time parameters may not be varargs, use untyped macro varargs '...' instead."); return false; } param_kind = VARDECL_PARAM_CT; @@ -1316,9 +1316,9 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Decl **body_params, return false; } // This will catch Type... &foo and &foo..., neither is allowed. - if (ellipsis || try_consume(c, TOKEN_ELLIPSIS)) + if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) { - PRINT_ERROR_HERE("Reference parameters may not be varargs, use untyped macro varargs '...' instead."); + PRINT_ERROR_LAST("Reference parameters may not be varargs, use untyped macro varargs '...' instead."); return false; } // Span includes the "&" @@ -1333,9 +1333,9 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Decl **body_params, // expression #foo name = symstr(c); advance_and_verify(c, TOKEN_HASH_IDENT); - if (ellipsis || try_consume(c, TOKEN_ELLIPSIS)) + if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) { - PRINT_ERROR_HERE("Expression parameters may not be varargs, use untyped macro varargs '...' instead."); + PRINT_ERROR_LAST("Expression parameters may not be varargs, use untyped macro varargs '...' instead."); return false; } param_kind = VARDECL_PARAM_EXPR; @@ -1344,9 +1344,9 @@ bool parse_parameters(ParseContext *c, Decl ***params_ref, Decl **body_params, case TOKEN_CT_TYPE_IDENT: name = symstr(c); advance_and_verify(c, TOKEN_CT_TYPE_IDENT); - if (ellipsis || try_consume(c, TOKEN_ELLIPSIS)) + if (ellipsis || tok_is(c, TOKEN_ELLIPSIS)) { - PRINT_ERROR_HERE("Expression parameters may not be varargs, use untyped macro varargs '...' instead."); + PRINT_ERROR_LAST("Expression parameters may not be varargs, use untyped macro varargs '...' instead."); return false; } param_kind = VARDECL_PARAM_CT_TYPE; diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 1b14f19f3..ba15e1fa7 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -1296,9 +1296,11 @@ Ast *parse_stmt(ParseContext *c) case TOKEN_BYTES: case TOKEN_CHAR_LITERAL: case TOKEN_CT_ALIGNOF: + case TOKEN_CT_ANDFN: case TOKEN_CT_AND: case TOKEN_CT_APPEND: case TOKEN_CT_ASSIGNABLE: + case TOKEN_CT_CONCATFN: case TOKEN_CT_CONCAT: case TOKEN_CT_CONST_IDENT: case TOKEN_CT_IS_CONST: @@ -1310,6 +1312,7 @@ Ast *parse_stmt(ParseContext *c) case TOKEN_CT_IDENT: case TOKEN_CT_NAMEOF: case TOKEN_CT_OFFSETOF: + case TOKEN_CT_ORFN: case TOKEN_CT_OR: case TOKEN_CT_QNAMEOF: case TOKEN_CT_SIZEOF: diff --git a/src/compiler/sema_casts.c b/src/compiler/sema_casts.c index c452dbee1..2d9507ba7 100644 --- a/src/compiler/sema_casts.c +++ b/src/compiler/sema_casts.c @@ -443,6 +443,11 @@ RETRY: case BINARYOP_EQ: // This type is bool, so check should never happen. UNREACHABLE + case BINARYOP_CT_OR: + case BINARYOP_CT_AND: + case BINARYOP_CT_CONCAT: + // This should be folded already. + UNREACHABLE } UNREACHABLE case EXPR_BUILTIN_ACCESS: diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index cfa204b3b..03e534d3c 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -6101,7 +6101,6 @@ static bool sema_expr_analyse_and_or(SemaContext *context, Expr *expr, Expr *lef if (!sema_binary_analyse_subexpr(context, expr, left, right)) return false; if (!cast_explicit(context, left, type_bool) || !cast_explicit(context, right, type_bool)) return false; - if (expr_both_const(left, right) && sema_constant_fold_ops(left)) { if (expr->binary_expr.operator == BINARYOP_AND) @@ -6878,6 +6877,513 @@ static inline bool sema_expr_analyse_or_error(SemaContext *context, Expr *expr) return true; } +static inline bool sema_expr_analyse_ct_and_or(SemaContext *context, Expr *expr, Expr *left, Expr *right) +{ + assert(expr->resolve_status == RESOLVE_RUNNING); + bool is_and = expr->binary_expr.operator == BINARYOP_CT_AND; + if (!sema_analyse_expr(context, left)) return false; + if (!expr_is_const_bool(left)) RETURN_SEMA_ERROR(left, "Expected this to evaluate to a constant boolean."); + if (left->const_expr.b != is_and) + { + expr_rewrite_const_bool(expr, type_bool, !is_and); + return true; + } + if (!sema_analyse_expr(context, right)) return false; + if (!expr_is_const_bool(right)) RETURN_SEMA_ERROR(right, "Expected this to evaluate to a constant boolean."); + expr_rewrite_const_bool(expr, type_bool, right->const_expr.b); + return true; +} + +typedef enum ConcatType_ +{ + CONCAT_UNKNOWN, + CONCAT_JOIN_BYTES, + CONCAT_JOIN_ARRAYS, + CONCAT_JOIN_LISTS, +} ConcatType; + + +static bool sema_append_const_array_element(SemaContext *context, Expr *expr, Expr *list, Expr **exprs) +{ + bool is_empty_slice = list->const_expr.const_kind == CONST_POINTER; + if (!is_empty_slice && list->const_expr.initializer->kind != CONST_INIT_ARRAY_FULL) + { + RETURN_SEMA_ERROR(list, "Only fully initialized arrays may be appended to."); + } + Type *array_type = type_flatten(list->type); + bool is_vector = array_type->type_kind == TYPE_VECTOR || array_type->type_kind == TYPE_INFERRED_VECTOR; + unsigned len = (is_empty_slice ? 0 : sema_get_const_len(context, list)) + vec_size(exprs) - 1; + Type *indexed = type_get_indexed_type(list->type); + Type *new_type = is_vector ? type_get_vector(indexed, len) : type_get_array(indexed, len); + ConstInitializer *init = list->const_expr.initializer; + ConstInitializer **inits = VECNEW(ConstInitializer*, len); + if (!is_empty_slice) + { + FOREACH(ConstInitializer *, i, init->init_array_full) vec_add(inits, i); + } + unsigned elements = vec_size(exprs); + for (unsigned i = 1; i < elements; i++) + { + Expr *element = exprs[i]; + if (!sema_analyse_inferred_expr(context, indexed, element)) return false; + if (!cast_implicit(context, element, indexed, false)) return false; + ConstInitializer *in = CALLOCS(ConstInitializer); + in->kind = CONST_INIT_VALUE; + in->init_value = element; + vec_add(inits, in); + } + expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; + expr->type = new_type; + ConstInitializer *new_init = CALLOCS(ConstInitializer); + new_init->init_array_full = inits; + new_init->type = new_type; + new_init->kind = CONST_INIT_ARRAY_FULL; + expr->const_expr = (ExprConst) { + .const_kind = CONST_INITIALIZER, + .initializer = new_init + }; + return true; +} + +static bool sema_append_const_array_one(SemaContext *context, Expr *expr, Expr *list, Expr *element) +{ + bool is_empty_slice = list->const_expr.const_kind == CONST_POINTER; + if (!is_empty_slice && list->const_expr.initializer->kind != CONST_INIT_ARRAY_FULL) + { + RETURN_SEMA_ERROR(list, "Only fully initialized arrays may be appended to."); + } + Type *array_type = type_flatten(list->type); + bool is_vector = array_type->type_kind == TYPE_VECTOR || array_type->type_kind == TYPE_INFERRED_VECTOR; + unsigned len = (is_empty_slice ? 0 : sema_get_const_len(context, list)) + 1; + Type *indexed = type_get_indexed_type(list->type); + Type *new_type = is_vector ? type_get_vector(indexed, len) : type_get_array(indexed, len); + ConstInitializer *init = list->const_expr.initializer; + ConstInitializer **inits = VECNEW(ConstInitializer*, len); + if (!is_empty_slice) + { + FOREACH(ConstInitializer *, i, init->init_array_full) vec_add(inits, i); + } + + assert(element->resolve_status == RESOLVE_DONE); + if (!cast_implicit(context, element, indexed, false)) return false; + ConstInitializer *in = CALLOCS(ConstInitializer); + in->kind = CONST_INIT_VALUE; + in->init_value = element; + vec_add(inits, in); + expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; + expr->type = new_type; + ConstInitializer *new_init = CALLOCS(ConstInitializer); + new_init->init_array_full = inits; + new_init->type = new_type; + new_init->kind = CONST_INIT_ARRAY_FULL; + expr->const_expr = (ExprConst) { + .const_kind = CONST_INITIALIZER, + .initializer = new_init + }; + return true; +} + +static bool sema_concat_const_bytes(SemaContext *context, Expr *expr, Type *type, bool is_bytes, const char *a, const char *b, ArraySize alen, ArraySize blen) +{ + Type *indexed = type_get_indexed_type(type); + char *data = malloc_arena(alen + blen + 1); + char *current = data; + if (alen) memcpy(current, a, alen); + current += alen; + if (blen) memcpy(current, b, blen); + current += blen; + current[0] = '\0'; + expr->expr_kind = EXPR_CONST; + expr->const_expr = (ExprConst) { + .const_kind = is_bytes ? CONST_BYTES : CONST_STRING, + .bytes.ptr = data, + .bytes.len = alen + blen + }; + expr->resolve_status = RESOLVE_DONE; + expr->type = is_bytes ? type_get_array(indexed, alen + blen) : type; + return true; +} + +static bool sema_concat_character(SemaContext *context, Expr *expr, Type *type, const char *a, ArraySize alen, uint64_t c) +{ + char append_array[4]; + int len; + if (c <= 0x7f) + { + append_array[0] = (char) c; + len = 1; + } + else if (c <= 0x7ff) + { + append_array[0] = (char) (0xC0 | (c >> 6)); + append_array[1] = (char) (0x80 | (c & 0x3F)); + len = 2; + } + else if (c <= 0xffff) + { + append_array[0] = (char) (0xE0 | (c >> 12)); + append_array[1] = (char) (0x80 | ((c >> 6) & 0x3F)); + append_array[2] = (char) (0x80 | (c & 0x3F)); + len = 3; + } + else + { + append_array[0] = (char) (0xF0 | (c >> 18)); + append_array[1] = (char) (0x80 | ((c >> 12) & 0x3F)); + append_array[2] = (char) (0x80 | ((c >> 6) & 0x3F)); + append_array[3] = (char) (0x80 | (c & 0x3F)); + len = 4; + } + return sema_concat_const_bytes(context, expr, type, false, a, append_array, alen, len); +} + +/** + * 1. String + Bytes|String + * 2. Bytes + Bytes|String + * 3. Bytes + (i)char => Bytes + * 4. String + character => String + * 5. String + (i)char array/vector => String // Disallowed for now + * 6. Bytes + (i)char array/vector => Bytes // Disallowed for now + */ +static bool sema_concat_bytes_and_other(SemaContext *context, Expr *expr, Expr *left, Expr *right) +{ + ArraySize len = left->const_expr.bytes.len; + bool is_bytes = left->const_expr.const_kind == CONST_BYTES; + Type *indexed = type_get_indexed_type(left->type); + const char *left_bytes = left->const_expr.bytes.ptr; +RETRY:; + switch (right->const_expr.const_kind) + { + case CONST_INTEGER: + if (is_bytes) + { + // 2. Bytes + (i)char => Bytes + if (!cast_implicit(context, right, type_char, false)) return false; + char c = (char) int_to_i64(right->const_expr.ixx); + return sema_concat_const_bytes(context, expr, left->type, true, left_bytes, &c, len, 1); + } + // 1. String + character => String + if (int_is_neg(right->const_expr.ixx) || int_icomp(right->const_expr.ixx, 0x10FFFF, BINARYOP_GT)) + { + RETURN_SEMA_ERROR(right, "Cannot concatenate a string with an non-unicode value."); + } + return sema_concat_character(context, expr, left->type, left_bytes, len, right->const_expr.ixx.i.low); + case CONST_FLOAT: + case CONST_BOOL: + case CONST_ENUM: + case CONST_ERR: + case CONST_POINTER: + case CONST_TYPEID: + case CONST_MEMBER: + RETURN_SEMA_ERROR(expr, "Concatenating %s with %s is not possible at compile time.", + type_quoted_error_string(left->type), type_to_error_string(right->type)); + case CONST_BYTES: + case CONST_STRING: + return sema_concat_const_bytes(context, expr, left->type, is_bytes, left_bytes, + right->const_expr.bytes.ptr, + len, right->const_expr.bytes.len); + case CONST_UNTYPED_LIST: + if (!cast_implicit(context, right, type_get_inferred_array(indexed), false)) return false; + goto RETRY; + case CONST_INITIALIZER: + if (!cast_implicit(context, right, type_get_inferred_array(indexed), false)) return false; + expr_contract_array(&right->const_expr, left->const_expr.const_kind); + goto RETRY; + } + +} +static bool sema_append_concat_const_bytes(SemaContext *context, Expr *expr, Expr *list, Expr *element, bool is_append) +{ + Type *indexed = type_get_indexed_type(list->type); + assert(indexed && "This should always work"); + if (is_append && !cast_implicit(context, element, indexed, false)) return false; + size_t str_len = list->const_expr.bytes.len; + size_t element_len = is_append ? 1 : element->const_expr.bytes.len; + bool is_bytes = list->const_expr.const_kind == CONST_BYTES; + char *data = malloc_arena(str_len + element_len + 1); + char *current = data; + if (str_len) memcpy(current, list->const_expr.bytes.ptr, str_len); + current += str_len; + if (is_append) + { + current[0] = (unsigned char)element->const_expr.ixx.i.low; + } + else + { + if (element_len) memcpy(current, element->const_expr.bytes.ptr, element_len); + } + current += element_len; + current[0] = '\0'; + expr->expr_kind = EXPR_CONST; + expr->const_expr = (ExprConst) { + .const_kind = list->const_expr.const_kind, + .bytes.ptr = data, + .bytes.len = str_len + element_len + }; + expr->resolve_status = RESOLVE_DONE; + expr->type = is_bytes ? type_get_array(indexed, str_len + element_len) : list->type; + return true; +/* + + bool is_empty_slice = list->const_expr.const_kind == CONST_POINTER; + if (!is_empty_slice && list->const_expr.initializer->kind != CONST_INIT_ARRAY_FULL) + { + RETURN_SEMA_ERROR(list, "Only fully initialized arrays may be appended to."); + } + Type *array_type = type_flatten(list->type); + bool is_vector = array_type->type_kind == TYPE_VECTOR || array_type->type_kind == TYPE_INFERRED_VECTOR; + unsigned len = (is_empty_slice ? 0 : sema_get_const_len(context, list)) + 1; + Type *indexed = type_get_indexed_type(list->type); + Type *new_type = is_vector ? type_get_vector(indexed, len) : type_get_array(indexed, len); + ConstInitializer *init = list->const_expr.initializer; + ConstInitializer **inits = VECNEW(ConstInitializer*, len); + if (!is_empty_slice) + { + FOREACH(ConstInitializer *, i, init->init_array_full) vec_add(inits, i); + } + + assert(element->resolve_status == RESOLVE_DONE); + if (!cast_implicit(context, element, indexed, false)) return false; + ConstInitializer *in = CALLOCS(ConstInitializer); + in->kind = CONST_INIT_VALUE; + in->init_value = element; + vec_add(inits, in); + expr->expr_kind = EXPR_CONST; + expr->resolve_status = RESOLVE_DONE; + expr->type = new_type; + ConstInitializer *new_init = CALLOCS(ConstInitializer); + new_init->init_array_full = inits; + new_init->type = new_type; + new_init->kind = CONST_INIT_ARRAY_FULL; + expr->const_expr = (ExprConst) { + .const_kind = CONST_INITIALIZER, + .initializer = new_init + }; + return true;*/ +} + +static inline bool sema_expr_const_append(SemaContext *context, Expr *append_expr, Expr *list, Expr *element) +{ + Expr **untyped_list = NULL; + switch (list->const_expr.const_kind) + { + case CONST_INITIALIZER: + assert(list->type != type_untypedlist); + return sema_append_const_array_one(context, append_expr, list, element); + case CONST_UNTYPED_LIST: + untyped_list = list->const_expr.untyped_list; + break; + case CONST_POINTER: + if (list->type->canonical->type_kind == TYPE_SLICE) + { + return sema_append_const_array_one(context, append_expr, list, element); + } + FALLTHROUGH; + case CONST_BYTES: + case CONST_STRING: + return sema_append_concat_const_bytes(context, append_expr, list, element, true); + default: + RETURN_SEMA_ERROR(list, "Expected some kind of list or vector here."); + } + vec_add(untyped_list, element); + append_expr->expr_kind = EXPR_CONST; + append_expr->const_expr = (ExprConst) { .untyped_list = untyped_list, .const_kind = CONST_UNTYPED_LIST }; + append_expr->type = type_untypedlist; + append_expr->resolve_status = RESOLVE_DONE; + return true; +} + +/** + * The following valid cases exist: + * + * 1. String/Bytes + ... => String/Bytes + * 2. Vector/slice/array + Untyped list => Merged untyped list + * 3. Vector/slice/array + arraylike => vector/array iff canoncial match, otherwise Untyped list + * 4. Untyped list + Vector/array/slice => Merged untyped list + * 5. Vector/array/slice + element => Vector/array/slice + 1 len iff canonical match, Untyped list otherwise + * 6. Untyped list + element => Untyped list + */ +static inline bool sema_expr_analyse_ct_concat(SemaContext *context, Expr *concat_expr, Expr *left, Expr *right) +{ + assert(concat_expr->resolve_status == RESOLVE_RUNNING); + bool join_single = false; + ArraySize len = 0; + bool use_array = true; + Type *indexed_type = NULL; + if (!sema_analyse_expr(context, left)) return false; + if (!sema_cast_const(left)) RETURN_SEMA_ERROR(left, "Expected this to evaluate to a constant value."); + if (!sema_analyse_expr(context, right)) return false; + if (!sema_cast_const(right)) RETURN_SEMA_ERROR(left, "Expected this to evaluate to a constant value."); + Type *element_type = left->type->canonical; + Type *right_type = right->type->canonical; + ConstKind right_kind = right->const_expr.const_kind; + switch (left->const_expr.const_kind) + { + case CONST_POINTER: + if (element_type->type_kind == TYPE_SLICE) + { + len = 0; + indexed_type = type_get_indexed_type(element_type); + break; + } + FALLTHROUGH; + case CONST_FLOAT: + case CONST_INTEGER: + case CONST_BOOL: + case CONST_ENUM: + case CONST_ERR: + case CONST_TYPEID: + RETURN_SEMA_ERROR(left, "Only bytes, strings and list-like constants can be concatenated."); + case CONST_BYTES: + case CONST_STRING: + return sema_concat_bytes_and_other(context, concat_expr, left, right); + case CONST_INITIALIZER: + switch (type_flatten(element_type)->type_kind) + { + case TYPE_VECTOR: + case TYPE_INFERRED_VECTOR: + use_array = false; + FALLTHROUGH; + case TYPE_SLICE: + case TYPE_ARRAY: + case TYPE_INFERRED_ARRAY: + { + switch (left->const_expr.initializer->kind) + { + case CONST_INIT_ARRAY_FULL: + break; + case CONST_INIT_ZERO: + if (type_flatten(element_type)->type_kind == TYPE_SLICE) break; + FALLTHROUGH; + default: + RETURN_SEMA_ERROR(left, "Only fully initialized arrays may be concatenated."); + } + indexed_type = type_get_indexed_type(element_type); + assert(indexed_type); + len = sema_get_const_len(context, left); + break; + } + case TYPE_UNTYPED_LIST: + UNREACHABLE + default: + RETURN_SEMA_ERROR(left, "Only bytes, strings and array-like constants can be concatenated."); + } + break; + case CONST_UNTYPED_LIST: + len = vec_size(left->const_expr.untyped_list); + break; + case CONST_MEMBER: + RETURN_SEMA_ERROR(left, "This can't be concatenated."); + } + switch (right->const_expr.const_kind) + { + case CONST_FLOAT: + case CONST_INTEGER: + case CONST_BOOL: + case CONST_ENUM: + case CONST_ERR: + case CONST_POINTER: + case CONST_TYPEID: + case CONST_MEMBER: + return sema_expr_const_append(context, concat_expr, left, right); + case CONST_BYTES: + case CONST_STRING: + if (left->type == type_untypedlist || indexed_type == right_type) return sema_expr_const_append(context, concat_expr, left, right); + if (!type_is_integer(indexed_type) || type_size(indexed_type) != 1) + { + RETURN_SEMA_ERROR(right, "You can't concatenate %s and %s.", type_quoted_error_string(left->type), + type_to_error_string(right_type)); + } + if (!cast_implicit(context, right, type_get_inferred_array(indexed_type), false)) return false; + expr_contract_array(&left->const_expr, CONST_BYTES); + return sema_concat_bytes_and_other(context, concat_expr, left, right); + case CONST_UNTYPED_LIST: + break; + case CONST_INITIALIZER: + if (indexed_type && cast_implicit_silent(context, right, indexed_type, false)) + { + return sema_expr_const_append(context, concat_expr, left, right); + } + break; + } + if (indexed_type && !cast_implicit_silent(context, right, type_get_inferred_array(indexed_type), false)) + { + indexed_type = NULL; + } + len += sema_get_const_len(context, right); + if (!indexed_type) + { + Expr **untyped_exprs = VECNEW(Expr*, len + 1); + Expr *exprs[2] = { left, right }; + for (unsigned i = 0; i < 2; i++) + { + Expr *single_expr = exprs[i]; + if (expr_is_const_untyped_list(single_expr)) + { + FOREACH(Expr *, expr_untyped, single_expr->const_expr.untyped_list) + { + vec_add(untyped_exprs, expr_untyped); + } + continue; + } + ConstInitializer *init = single_expr->const_expr.initializer; + if (init->kind != CONST_INIT_ARRAY_FULL) + { + if (init->kind == CONST_INIT_ZERO && init->type == type_untypedlist) continue; + RETURN_SEMA_ERROR(single_expr, "Expected a full array here."); + } + FOREACH(ConstInitializer *, val, init->init_array_full) + { + vec_add(untyped_exprs, val->init_value); + } + } + concat_expr->expr_kind = EXPR_CONST; + concat_expr->type = type_untypedlist; + concat_expr->resolve_status = RESOLVE_DONE; + concat_expr->const_expr = (ExprConst) { + .const_kind = CONST_UNTYPED_LIST, + .untyped_list = untyped_exprs + }; + return true; + } + ConstInitializer **inits = VECNEW(ConstInitializer*, len); + Expr *exprs[2] = { left, right }; + for (int i = 0; i < 2; i++) + { + Expr *element = exprs[i]; + assert(element->const_expr.const_kind == CONST_INITIALIZER); + ConstInitType init_type = element->const_expr.initializer->kind; + switch (init_type) + { + case CONST_INIT_ARRAY_FULL: + break; + case CONST_INIT_ZERO: + if (type_flatten(element->type)->type_kind == TYPE_SLICE) continue; + default: + RETURN_SEMA_ERROR(element, "Only fully initialized arrays may be concatenated."); + } + FOREACH(ConstInitializer *, init, element->const_expr.initializer->init_array_full) + { + vec_add(inits, init); + } + } + concat_expr->expr_kind = EXPR_CONST; + concat_expr->resolve_status = RESOLVE_DONE; + concat_expr->type = use_array ? type_get_array(indexed_type, len) : type_get_vector(indexed_type, len); + ConstInitializer *new_init = CALLOCS(ConstInitializer); + new_init->init_array_full = inits; + new_init->type = concat_expr->type; + new_init->kind = CONST_INIT_ARRAY_FULL; + concat_expr->const_expr = (ExprConst) { + .const_kind = CONST_INITIALIZER, + .initializer = new_init + }; + return true; +} + + static inline bool sema_expr_analyse_binary(SemaContext *context, Expr *expr) { if (expr->binary_expr.operator == BINARYOP_ELSE) return sema_expr_analyse_or_error(context, expr); @@ -6894,6 +7400,11 @@ static inline bool sema_expr_analyse_binary(SemaContext *context, Expr *expr) { case BINARYOP_ELSE: UNREACHABLE // Handled previously + case BINARYOP_CT_CONCAT: + return sema_expr_analyse_ct_concat(context, expr, left, right); + case BINARYOP_CT_OR: + case BINARYOP_CT_AND: + return sema_expr_analyse_ct_and_or(context, expr, left, right); case BINARYOP_ASSIGN: return sema_expr_analyse_assign(context, expr, left, right); case BINARYOP_MULT: @@ -8453,7 +8964,7 @@ static inline bool sema_expr_analyse_castable(SemaContext *context, Expr *expr) } -static inline bool sema_expr_analyse_ct_and_or(SemaContext *context, Expr *expr) +static inline bool sema_expr_analyse_ct_and_or_fn(SemaContext *context, Expr *expr) { assert(expr->resolve_status == RESOLVE_RUNNING); bool is_and = expr->ct_and_or_expr.is_and; @@ -8473,15 +8984,6 @@ static inline bool sema_expr_analyse_ct_and_or(SemaContext *context, Expr *expr) return true; } -typedef enum ConcatType_ -{ - CONCAT_UNKNOWN, - CONCAT_JOIN_BYTES, - CONCAT_JOIN_ARRAYS, - CONCAT_JOIN_LISTS, -} ConcatType; - - bool sema_concat_join_arrays(SemaContext *context, Expr *expr, Expr **exprs, Type *type, ArraySize len) { ConstInitializer **inits = VECNEW(ConstInitializer*, len); @@ -8517,7 +9019,7 @@ bool sema_concat_join_arrays(SemaContext *context, Expr *expr, Expr **exprs, Typ return true; } -bool sema_append_const_array(SemaContext *context, Expr *expr, Expr *list, Expr **exprs) +static bool sema_append_const_array(SemaContext *context, Expr *expr, Expr *list, Expr **exprs) { bool is_empty_slice = list->const_expr.const_kind == CONST_POINTER; if (!is_empty_slice && list->const_expr.initializer->kind != CONST_INIT_ARRAY_FULL) @@ -8584,7 +9086,7 @@ bool sema_concat_join_bytes(Expr *expr, Expr **exprs, ArraySize len) return true; } -static inline bool sema_expr_analyse_ct_concat(SemaContext *context, Expr *concat_expr) +static inline bool sema_expr_analyse_ct_concatfn(SemaContext *context, Expr *concat_expr) { assert(concat_expr->resolve_status == RESOLVE_RUNNING); if (!sema_expand_vasplat_exprs(context, concat_expr->ct_concat)) return false; @@ -9000,9 +9502,9 @@ static inline bool sema_analyse_expr_dispatch(SemaContext *context, Expr *expr) case EXPR_CT_APPEND: return sema_expr_analyse_ct_append(context, expr); case EXPR_CT_CONCAT: - return sema_expr_analyse_ct_concat(context, expr); + return sema_expr_analyse_ct_concatfn(context, expr); case EXPR_CT_AND_OR: - return sema_expr_analyse_ct_and_or(context, expr); + return sema_expr_analyse_ct_and_or_fn(context, expr); case EXPR_CT_ARG: return sema_expr_analyse_ct_arg(context, NULL, expr); case EXPR_STRINGIFY: diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index eb3916f2c..8348f423a 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -82,6 +82,12 @@ const char *token_type_to_string(TokenType type) return "^="; case TOKEN_BUILTIN: return "$$"; + case TOKEN_CT_AND: + return "&&&"; + case TOKEN_CT_OR: + return "|||"; + case TOKEN_CT_CONCAT: + return "+++"; case TOKEN_DIV_ASSIGN: return "/="; case TOKEN_DOTDOT: @@ -326,7 +332,7 @@ const char *token_type_to_string(TokenType type) case TOKEN_CT_ALIGNOF: return "$alignof"; - case TOKEN_CT_AND: + case TOKEN_CT_ANDFN: return "$and"; case TOKEN_CT_APPEND: return "$append"; @@ -336,7 +342,7 @@ const char *token_type_to_string(TokenType type) return "$assignable"; case TOKEN_CT_CASE: return "$case"; - case TOKEN_CT_CONCAT: + case TOKEN_CT_CONCATFN: return "$concat"; case TOKEN_CT_DEFAULT: return "$default"; @@ -394,7 +400,7 @@ const char *token_type_to_string(TokenType type) return "$nameof"; case TOKEN_CT_OFFSETOF: return "$offsetof"; - case TOKEN_CT_OR: + case TOKEN_CT_ORFN: return "$or"; case TOKEN_CT_QNAMEOF: return "$qnameof"; diff --git a/src/utils/common.h b/src/utils/common.h index cce6449d4..8fb84d3f0 100644 --- a/src/utils/common.h +++ b/src/utils/common.h @@ -68,7 +68,7 @@ #if (defined(__GNUC__) && __GNUC__ >= 7) || defined(__clang__) #define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__)) -#define FALLTHROUGH __attribute__ ((fallthrough)) +#define FALLTHROUGH (void)1; __attribute__ ((fallthrough)) #define UNUSED __attribute__((unused)) #define NORETURN __attribute__((noreturn)) #define INLINE __attribute__((always_inline)) static inline diff --git a/test/test_suite/compile_time/concat_append.c3t b/test/test_suite/compile_time/concat_append.c3t index f28ff4999..9abf9989e 100644 --- a/test/test_suite/compile_time/concat_append.c3t +++ b/test/test_suite/compile_time/concat_append.c3t @@ -2,16 +2,16 @@ module test; macro foo() { - var c = $concat("hello", " world"); - String[*] a = $concat(String[1] { "hello" }, String[1] { " world" }); + var c = "hello" +++ " world"; + String[*] a = String[1] { "hello" } +++ String[1] { " world" }; int[2] $a = { 1, 2 }; - $a = $append($a, 100); + $a = $a +++ 100; int z = $typeof($a).len; var b = $a; - var d = $append(int[]{}, 1, 2, 3); - var e = $append(String[1] { "hello... " }, " there!"); - var f = $concat("", ""); - var g = $concat("bye"); + var d = int[]{} +++ 1 +++ 2 +++ 3; + var e = String[1] { "hello... " } +++ " there!"; + var f = "" +++ ""; + var g = "bye"; var h = $$str_hash("helloworld"); } fn int main() diff --git a/test/test_suite/compile_time/concat_test_cases.c3 b/test/test_suite/compile_time/concat_test_cases.c3 new file mode 100644 index 000000000..9be8b1eab --- /dev/null +++ b/test/test_suite/compile_time/concat_test_cases.c3 @@ -0,0 +1,25 @@ +fn void main(String[] args) +{ + var $x = int[2]{ 1, 2 } +++ int[2]{ 4, 5 }; + var $v = "foo" +++ "baz" +++ '!' +++ '?'; + var $b = x'403322' +++ "baz"; + var $b2 = x'40334a' +++ 55 +++ 55; + var $b3 = x'403322' +++ char[2] { 1, 2 }; + var $b4 = x'403322' +++ char[2] { 1, 2 }; + var $b5 = "foo" +++ { 55, 57 }; + var $b6 = ((ichar[3])x'403322') +++ ichar[2] { 1, 2 }; + var $b7 = char[2] { 1, 2 } +++ "foo"; + assert($b7 == { 1, 2, 102, 111, 111}); + assert($b5 == "foo79"); + assert($b3 == $b4); + assert($b6 == { 0x40, 0x33, 0x22, 1, 2 }); + int[4] $x2 = int[2]{ 1, 2 }+++ int[2]{ 4, 5 }; + int[4] $y = { 1, 2 } +++ { 4, 5 }; + assert($x == {1, 2, 4, 5}); + assert($x2 == {1, 2, 4, 5}); + assert($y == {1, 2, 4, 5}); + assert($v == "foobaz!?"); + assert($b == {64, 51, 34, 98, 97, 122}); + assert($b2 == {64, 51, 74, 55, 55}); + assert($b3 == {64, 51, 34, 1, 2}); +} \ No newline at end of file diff --git a/test/test_suite/functions/macro_arguments.c3 b/test/test_suite/functions/macro_arguments.c3 index a2bc5b87d..fc205c9ee 100644 --- a/test/test_suite/functions/macro_arguments.c3 +++ b/test/test_suite/functions/macro_arguments.c3 @@ -10,3 +10,5 @@ fn void foo4($Type) { } // #error: Only regular parameters are allowed for funct fn void foo8(int* &foo) {} // #error: Only regular parameters are allowed for functions. fn void foo9(int x, int x) {} // #error: Duplicate parameter name 'x'. + +macro @foo($a, $b, $c, ...) {} \ No newline at end of file