diff --git a/doc/langref.md b/doc/langref.md index 9a704d0cd0..d1b3bba468 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -121,7 +121,7 @@ MultiplyExpression = CurlySuffixExpression MultiplyOperator MultiplyExpression | CurlySuffixExpression = TypeExpr option(ContainerInitExpression) -MultiplyOperator = "*" | "/" | "%" +MultiplyOperator = "*" | "/" | "%" | "**" PrefixOpExpression = PrefixOp PrefixOpExpression | SuffixOpExpression diff --git a/src/all_types.hpp b/src/all_types.hpp index 9867b4bfe0..0227ddf535 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -342,6 +342,7 @@ enum BinOpType { BinOpTypeMod, BinOpTypeUnwrapMaybe, BinOpTypeStrCat, + BinOpTypeArrayMult, }; struct AstNodeBinOpExpr { diff --git a/src/analyze.cpp b/src/analyze.cpp index 71bc19fabd..4d286fe03c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2937,6 +2937,7 @@ static bool is_op_allowed(TypeTableEntry *type, BinOpType op) { case BinOpTypeMod: case BinOpTypeUnwrapMaybe: case BinOpTypeStrCat: + case BinOpTypeArrayMult: zig_unreachable(); } zig_unreachable(); @@ -3121,9 +3122,93 @@ static TypeTableEntry *analyze_logic_bin_op_expr(CodeGen *g, ImportTableEntry *i return g->builtin_types.entry_bool; } +static TypeTableEntry *analyze_array_mult(CodeGen *g, ImportTableEntry *import, BlockContext *context, + TypeTableEntry *expected_type, AstNode *node) +{ + assert(node->type == NodeTypeBinOpExpr); + assert(node->data.bin_op_expr.bin_op == BinOpTypeArrayMult); + + AstNode **op1 = node->data.bin_op_expr.op1->parent_field; + AstNode **op2 = node->data.bin_op_expr.op2->parent_field; + + TypeTableEntry *op1_type = analyze_expression(g, import, context, nullptr, *op1); + TypeTableEntry *op2_type = analyze_expression(g, import, context, nullptr, *op2); + + if (op1_type->id == TypeTableEntryIdInvalid || + op2_type->id == TypeTableEntryIdInvalid) + { + return g->builtin_types.entry_invalid; + } + + ConstExprValue *op1_val = &get_resolved_expr(*op1)->const_val; + ConstExprValue *op2_val = &get_resolved_expr(*op2)->const_val; + + AstNode *bad_node; + if (!op1_val->ok) { + bad_node = *op1; + } else if (!op2_val->ok) { + bad_node = *op2; + } else { + bad_node = nullptr; + } + if (bad_node) { + add_node_error(g, bad_node, buf_sprintf("array multiplication requires constant expression")); + return g->builtin_types.entry_invalid; + } + + if (op1_type->id != TypeTableEntryIdArray) { + add_node_error(g, *op1, + buf_sprintf("expected array type, got '%s'", buf_ptr(&op1_type->name))); + return g->builtin_types.entry_invalid; + } + + if (op2_type->id != TypeTableEntryIdNumLitInt && + op2_type->id != TypeTableEntryIdInt) + { + add_node_error(g, *op2, buf_sprintf("expected integer type, got '%s'", buf_ptr(&op2_type->name))); + return g->builtin_types.entry_invalid; + } + + if (op2_val->data.x_bignum.is_negative) { + add_node_error(g, *op2, buf_sprintf("expected positive number")); + return g->builtin_types.entry_invalid; + } + + ConstExprValue *const_val = &get_resolved_expr(node)->const_val; + const_val->ok = true; + const_val->depends_on_compile_var = op1_val->depends_on_compile_var || op2_val->depends_on_compile_var; + + TypeTableEntry *child_type = op1_type->data.array.child_type; + BigNum old_array_len; + bignum_init_unsigned(&old_array_len, op1_type->data.array.len); + + BigNum new_array_len; + if (bignum_mul(&new_array_len, &old_array_len, &op2_val->data.x_bignum)) { + add_node_error(g, node, buf_sprintf("operation results in overflow")); + return g->builtin_types.entry_invalid; + } + + uint64_t old_array_len_bare = op1_type->data.array.len; + uint64_t operand_amt = op2_val->data.x_bignum.data.x_uint; + + uint64_t new_array_len_bare = new_array_len.data.x_uint; + const_val->data.x_array.fields = allocate(new_array_len_bare); + + uint64_t i = 0; + for (uint64_t x = 0; x < operand_amt; x += 1) { + for (uint64_t y = 0; y < old_array_len_bare; y += 1) { + const_val->data.x_array.fields[i] = op1_val->data.x_array.fields[y]; + i += 1; + } + } + + return get_array_type(g, child_type, new_array_len_bare); +} + static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, BlockContext *context, TypeTableEntry *expected_type, AstNode *node) { + assert(node->type == NodeTypeBinOpExpr); BinOpType bin_op_type = node->data.bin_op_expr.bin_op; switch (bin_op_type) { case BinOpTypeAssign: @@ -3320,6 +3405,8 @@ static TypeTableEntry *analyze_bin_op_expr(CodeGen *g, ImportTableEntry *import, return str_type; } + case BinOpTypeArrayMult: + return analyze_array_mult(g, import, context, expected_type, node); case BinOpTypeInvalid: zig_unreachable(); } diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 3686a9af22..d9112715b5 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -38,6 +38,7 @@ static const char *bin_op_str(BinOpType bin_op) { case BinOpTypeAssignBoolOr: return "||="; case BinOpTypeUnwrapMaybe: return "??"; case BinOpTypeStrCat: return "++"; + case BinOpTypeArrayMult: return "**"; } zig_unreachable(); } diff --git a/src/codegen.cpp b/src/codegen.cpp index 487c3ba370..5d97d00616 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1466,6 +1466,7 @@ static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node, case BinOpTypeAssignBoolOr: case BinOpTypeUnwrapMaybe: case BinOpTypeStrCat: + case BinOpTypeArrayMult: zig_unreachable(); } zig_unreachable(); @@ -1772,6 +1773,7 @@ static LLVMValueRef gen_bin_op_expr(CodeGen *g, AstNode *node) { switch (node->data.bin_op_expr.bin_op) { case BinOpTypeInvalid: case BinOpTypeStrCat: + case BinOpTypeArrayMult: zig_unreachable(); case BinOpTypeAssign: case BinOpTypeAssignTimes: diff --git a/src/eval.cpp b/src/eval.cpp index f9bc5d8a84..445004e57b 100644 --- a/src/eval.cpp +++ b/src/eval.cpp @@ -232,7 +232,7 @@ int eval_const_expr_bin_op(ConstExprValue *op1_val, TypeTableEntry *op1_type, case BinOpTypeUnwrapMaybe: zig_panic("TODO"); case BinOpTypeStrCat: - zig_panic("TODO"); + case BinOpTypeArrayMult: case BinOpTypeInvalid: zig_unreachable(); } diff --git a/src/parser.cpp b/src/parser.cpp index 603bb990b9..62f83fd080 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -1297,6 +1297,7 @@ static PrefixOp tok_to_prefix_op(Token *token) { case TokenIdPercentPercent: return PrefixOpUnwrapError; case TokenIdDoubleQuestion: return PrefixOpUnwrapMaybe; case TokenIdBoolAnd: return PrefixOpAddressOf; + case TokenIdStarStar: return PrefixOpDereference; default: return PrefixOpInvalid; } } @@ -1331,6 +1332,14 @@ static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, int *token_index, boo parent_node->data.prefix_op_expr.primary_expr = node; parent_node->data.prefix_op_expr.prefix_op = PrefixOpAddressOf; + node->column += 1; + } else if (token->id == TokenIdStarStar) { + // pretend that we got 2 star tokens + + parent_node = ast_create_node(pc, NodeTypePrefixOpExpr, token); + parent_node->data.prefix_op_expr.primary_expr = node; + parent_node->data.prefix_op_expr.prefix_op = PrefixOpDereference; + node->column += 1; } @@ -1355,6 +1364,7 @@ static AstNode *ast_parse_prefix_op_expr(ParseContext *pc, int *token_index, boo static BinOpType tok_to_mult_op(Token *token) { switch (token->id) { case TokenIdStar: return BinOpTypeMult; + case TokenIdStarStar: return BinOpTypeArrayMult; case TokenIdSlash: return BinOpTypeDiv; case TokenIdPercent: return BinOpTypeMod; default: return BinOpTypeInvalid; @@ -1362,7 +1372,7 @@ static BinOpType tok_to_mult_op(Token *token) { } /* -MultiplyOperator : token(Star) | token(Slash) | token(Percent) +MultiplyOperator = "*" | "/" | "%" | "**" */ static BinOpType ast_parse_mult_op(ParseContext *pc, int *token_index, bool mandatory) { Token *token = &pc->tokens->at(*token_index); diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp index 79b87c4536..373f815857 100644 --- a/src/tokenizer.cpp +++ b/src/tokenizer.cpp @@ -626,6 +626,11 @@ void tokenize(Buf *buf, Tokenization *out) { end_token(&t); t.state = TokenizeStateStart; break; + case '*': + t.cur_tok->id = TokenIdStarStar; + end_token(&t); + t.state = TokenizeStateStart; + break; default: t.pos -= 1; end_token(&t); @@ -1235,6 +1240,7 @@ const char * token_name(TokenId id) { case TokenIdRParen: return ")"; case TokenIdComma: return ","; case TokenIdStar: return "*"; + case TokenIdStarStar: return "**"; case TokenIdLBrace: return "{"; case TokenIdRBrace: return "}"; case TokenIdLBracket: return "["; diff --git a/src/tokenizer.hpp b/src/tokenizer.hpp index eac7b0a69b..db59f47305 100644 --- a/src/tokenizer.hpp +++ b/src/tokenizer.hpp @@ -47,6 +47,7 @@ enum TokenId { TokenIdRParen, TokenIdComma, TokenIdStar, + TokenIdStarStar, TokenIdLBrace, TokenIdRBrace, TokenIdLBracket, diff --git a/test/self_hosted.zig b/test/self_hosted.zig index a078122335..4a787ac665 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -1392,3 +1392,9 @@ fn test_take_address_of_parameter_noeval(f: f32) { const f_ptr = &f; assert(*f_ptr == 12.34); } + + +#attribute("test") +fn array_mult_operator() { + assert(str.eql("ab" ** 5, "ababababab")); +}