From 0f3332b1f3c761cd5215d968b3afef24cd3dc3cb Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Tue, 14 Apr 2026 12:45:31 +0300 Subject: [PATCH] all: Add power operator `**` (fixes #24067) --- doc/docs.md | 9 ++- vlib/v/checker/assign.v | 8 ++- vlib/v/checker/comptime.v | 26 ++++++++ vlib/v/checker/fn.v | 2 +- vlib/v/checker/infix.v | 12 +++- vlib/v/gen/c/assign.v | 92 +++++++++++++++++++++++++--- vlib/v/gen/c/cgen.v | 48 ++++++++++++++- vlib/v/gen/c/fn.v | 2 +- vlib/v/gen/c/infix.v | 76 ++++++++++++++++++++++- vlib/v/gen/c/utils.v | 2 +- vlib/v/gen/js/fn.v | 4 +- vlib/v/gen/js/infix.v | 25 +++++++- vlib/v/gen/js/js.v | 72 +++++++++++++++++++++- vlib/v/parser/assign.v | 3 + vlib/v/parser/expr.v | 29 +++++++-- vlib/v/parser/fn.v | 7 ++- vlib/v/scanner/scanner.v | 8 +++ vlib/v/tests/power_operator_test.v | 48 +++++++++++++++ vlib/v/token/token.v | 19 ++++-- vlib/v/transformer/transformer.v | 12 ++++ vlib/v/type_resolver/type_resolver.v | 4 +- vlib/v/util/util.v | 1 + 22 files changed, 473 insertions(+), 36 deletions(-) create mode 100644 vlib/v/tests/power_operator_test.v diff --git a/doc/docs.md b/doc/docs.md index f65044ddd..f1f1ae6f8 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -7418,7 +7418,8 @@ fn main() { > > `a.add(b).add(c.mul(d))` is a lot less readable than `a + b + c * d`. -Operator overloading is possible for the following binary operators: `+, -, *, /, %, <, ==`. +Operator overloading is possible for the following binary operators: +`+, -, *, **, /, %, <, ==`. ### Implicitly generated overloads @@ -7426,8 +7427,8 @@ Operator overloading is possible for the following binary operators: `+, -, *, / - `!=`, `>`, `<=` and `>=` are automatically generated when `==` and `<` are defined. They cannot be explicitly overridden. -- Assignment operators (`*=`, `+=`, `/=`, etc) are automatically generated when the corresponding - operators are defined and the operands are of the same type. +- Assignment operators (`*=`, `**=`, `+=`, `/=`, etc) are automatically generated when the + corresponding operators are defined and the operands are of the same type. They cannot be explicitly overridden. ### Restriction @@ -8731,6 +8732,7 @@ This lists operators for [primitive types](#primitive-types) only. + sum integers, floats, strings - difference integers, floats * product integers, floats +** power integers, floats / quotient integers, floats % remainder integers @@ -8750,6 +8752,7 @@ This lists operators for [primitive types](#primitive-types) only. Precedence Operator + 6 ** 5 * / % << >> >>> & 4 + - | ^ 3 == != < <= > >= diff --git a/vlib/v/checker/assign.v b/vlib/v/checker/assign.v index 9b9253ff0..e4c01f6ce 100644 --- a/vlib/v/checker/assign.v +++ b/vlib/v/checker/assign.v @@ -783,7 +783,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', right.pos()) } } - .mult_assign, .div_assign { + .mult_assign, .power_assign, .div_assign { if !left_sym.is_number() && !c.table.final_sym(left_type_unwrapped).is_int() && left_sym.kind !in [.struct, .alias] { c.error('operator ${node.op.str()} not defined on left operand type `${left_sym.name}`', @@ -878,7 +878,10 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', } else {} } - if node.op in [.plus_assign, .minus_assign, .mod_assign, .mult_assign, .div_assign] + if node.op == .power_assign { + c.markused_power_runtime_support() + } + if node.op in [.plus_assign, .minus_assign, .mod_assign, .mult_assign, .power_assign, .div_assign] && (left_sym.kind == .alias || (left_sym.kind == .struct && right_sym.kind == .struct)) { left_name := c.table.type_to_str(left_type_unwrapped) right_name := c.table.type_to_str(right_type_unwrapped) @@ -894,6 +897,7 @@ or use an explicit `unsafe{ a[..] }`, if you do not want a copy of the slice.', .div_assign { '/' } .mod_assign { '%' } .mult_assign { '*' } + .power_assign { '**' } else { 'unknown op' } } if left_sym.kind == .struct && (left_sym.info as ast.Struct).generic_types.len > 0 { diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 48b14f14e..a5a7eed7a 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -2,6 +2,7 @@ // Use of this source code is governed by an MIT license that can be found in the LICENSE file. module checker +import math import os import v.ast import v.pref @@ -12,6 +13,28 @@ import v.type_resolver import v.errors import strings +fn comptime_power_i64(base i64, exponent i64) i64 { + return math.powi(base, exponent) +} + +fn comptime_power_f64(base f64, exponent f64) f64 { + return math.pow(base, exponent) +} + +fn comptime_power_value(left ast.ComptTimeConstValue, right ast.ComptTimeConstValue) ?ast.ComptTimeConstValue { + if left_i := left.i64() { + if right_i := right.i64() { + return comptime_power_i64(left_i, right_i) + } + } + if left_f := left.f64() { + if right_f := right.f64() { + return comptime_power_f64(left_f, right_f) + } + } + return none +} + fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { if node.left !is ast.EmptyExpr { node.left_type = c.expr(mut node.left) @@ -817,6 +840,9 @@ fn (mut c Checker) eval_comptime_const_expr_with_locals(expr ast.Expr, nlevel in } right := c.eval_comptime_const_expr_with_locals(expr.right, nlevel + 1, local_values)? c.expected_type = saved_expected_type + if expr.op == .power { + return comptime_power_value(left, right) + } if left is string && right is string { match expr.op { .plus { diff --git a/vlib/v/checker/fn.v b/vlib/v/checker/fn.v index ab8637c82..9c82e134d 100644 --- a/vlib/v/checker/fn.v +++ b/vlib/v/checker/fn.v @@ -736,7 +736,7 @@ fn (mut c Checker) fn_decl(mut node ast.FnDecl) { } else if parent_sym.is_primitive() { if node.return_type.has_option_or_result() { c.error('return type cannot be Option or Result', node.return_type_pos) - } else if node.name in ['+', '-', '*', '%', '/'] + } else if node.name in ['+', '-', '*', '**', '%', '/'] && node.return_type != receiver_type { srtype := c.table.type_to_str(receiver_type) c.error('operator `${node.name}` methods on primitive aliases should return `${srtype}`', diff --git a/vlib/v/checker/infix.v b/vlib/v/checker/infix.v index 0fdd1e992..a0576e07f 100644 --- a/vlib/v/checker/infix.v +++ b/vlib/v/checker/infix.v @@ -3,6 +3,11 @@ module checker import v.ast import v.token +fn (mut c Checker) markused_power_runtime_support() { + // C/JS backends lower `**` directly at codegen time, so there is no + // function usage to mark here. +} + fn infix_expr_allows_auto_deref(expr ast.Expr) bool { return expr is ast.Ident && expr.is_auto_deref_var() } @@ -250,7 +255,7 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { } if left_type.is_any_kind_of_pointer() && !left_type.has_flag(.shared_f) && !node.left.is_auto_deref_var() - && node.op in [.plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe] { + && node.op in [.plus, .minus, .mul, .power, .div, .mod, .xor, .amp, .pipe] { if !c.pref.translated && ((right_type.is_any_kind_of_pointer() && node.op != .minus) || (!right_type.is_any_kind_of_pointer() && node.op !in [.plus, .minus])) { if _ := left_sym.find_method(node.op.str()) { @@ -445,8 +450,11 @@ fn (mut c Checker) infix_expr(mut node ast.InfixExpr) ast.Type { node.promoted_type = ast.bool_type return ast.bool_type } - .plus, .minus, .mul, .div, .mod, .xor, .amp, .pipe { + .plus, .minus, .mul, .power, .div, .mod, .xor, .amp, .pipe { // binary operators that expect matching types + if node.op == .power { + c.markused_power_runtime_support() + } unwrapped_left_type := c.unwrap_generic(left_type) left_sym = c.table.sym(unwrapped_left_type) unwrapped_right_type := c.unwrap_generic(right_type) diff --git a/vlib/v/gen/c/assign.v b/vlib/v/gen/c/assign.v index 42aeac20b..d7eed6781 100644 --- a/vlib/v/gen/c/assign.v +++ b/vlib/v/gen/c/assign.v @@ -40,6 +40,69 @@ fn (mut g Gen) expr_in_value_context(expr ast.Expr, value_type ast.Type, expecte g.expr(expr_copy) } +fn (mut g Gen) write_assign_target_expr(left ast.Expr, var_type ast.Type) { + if left.is_auto_deref_var() && !var_type.has_flag(.shared_f) { + g.write('*') + } + g.expr(left) + if var_type.has_flag(.shared_f) { + g.write('->val') + } +} + +fn (mut g Gen) write_assign_value_expr(left ast.Expr, var_type ast.Type) { + old_is_assign_lhs := g.is_assign_lhs + g.is_assign_lhs = false + defer { + g.is_assign_lhs = old_is_assign_lhs + } + if left.is_auto_deref_var() && !var_type.has_flag(.shared_f) { + g.write('*') + } + g.expr(left) + if var_type.has_flag(.shared_f) { + g.write('->val') + } +} + +fn (mut g Gen) gen_power_assign_expr(left ast.Expr, left_type ast.Type, right ast.Expr, right_type ast.Type) { + power_result_type := g.normalized_power_result_type(left_type.clear_flag(.shared_f).clear_flag(.atomic_f), + left_type.clear_flag(.shared_f).clear_flag(.atomic_f), right_type) + builtin_power_type := g.table.unalias_num_type(power_result_type) + result_styp := g.styp(power_result_type) + g.uses_power = true + if builtin_power_type == ast.f32_type { + g.write('(${result_styp})powf((${g.styp(ast.f32_type)})(') + g.write_assign_value_expr(left, left_type) + g.write('), ') + g.expr_with_cast(right, right_type, ast.f32_type) + g.write(')') + return + } + if builtin_power_type.is_float() { + g.write('(${result_styp})pow((${g.styp(ast.f64_type)})(') + g.write_assign_value_expr(left, left_type) + g.write('), ') + g.expr_with_cast(right, right_type, ast.f64_type) + g.write(')') + return + } + if builtin_power_type.is_unsigned() { + g.uses_power_u64 = true + g.write('(${result_styp})__v_pow_u64((${g.styp(ast.u64_type)})(') + g.write_assign_value_expr(left, left_type) + g.write('), ') + g.expr_with_cast(right, right_type, ast.i64_type) + g.write(')') + return + } + g.write('(${result_styp})__v_pow_i64((${g.styp(ast.i64_type)})(') + g.write_assign_value_expr(left, left_type) + g.write('), ') + g.expr_with_cast(right, right_type, ast.i64_type) + g.write(')') +} + fn assign_expr_unwraps_option_or_result(expr ast.Expr) bool { return match expr { ast.CallExpr { expr.or_block.kind != .absent } @@ -736,8 +799,8 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { left.obj.typ = var_type g.assign_ct_type[val.pos.pos] = var_type } - } else if val is ast.InfixExpr && val.op in [.plus, .minus, .mul, .div, .mod] - && val.left_ct_expr { + } else if val is ast.InfixExpr + && val.op in [.plus, .minus, .mul, .power, .div, .mod] && val.left_ct_expr { left_ctyp := g.type_resolver.get_type_or_default(val.left, val.left_type) right_ctyp := g.type_resolver.get_type_or_default(val.right, val.right_type) ctyp := g.type_resolver.promote_type(g.unwrap_generic(left_ctyp), @@ -1343,15 +1406,19 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { str_add = true } // Assignment Operator Overloading - if ((left_sym.kind == .struct && right_sym.kind == .struct) + has_power_assign_overload := node.op == .power_assign + && left_sym.find_method_with_generic_parent('**') != none + if (((left_sym.kind == .struct && right_sym.kind == .struct) || (left_sym.kind == .alias && right_sym.kind == .alias)) - && node.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] { + && node.op in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .power_assign, .mod_assign]) + || has_power_assign_overload { extracted_op := match node.op { .plus_assign { '+' } .minus_assign { '-' } .div_assign { '/' } .mod_assign { '%' } .mult_assign { '*' } + .power_assign { '**' } else { 'unknown op' } } pos := g.out.len @@ -1374,9 +1441,13 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { && g.table.final_sym(g.unwrap_generic(var_type)).is_number() && !left_sym.has_method(extracted_op) { g.write(' = ') - g.expr(left) - g.write(' ${extracted_op} ') - g.expr(val) + if node.op == .power_assign { + g.gen_power_assign_expr(left, var_type, val, val_type) + } else { + g.expr(left) + g.write(' ${extracted_op} ') + g.expr(val) + } if !g.inside_for_c_stmt { g.write(';') } @@ -1453,6 +1524,13 @@ fn (mut g Gen) assign_stmt(node_ ast.AssignStmt) { g.writeln(';') return } + if node.op == .power_assign && !op_overloaded { + g.write_assign_target_expr(left, var_type) + g.write(' = ') + g.gen_power_assign_expr(left, var_type, val, val_type) + g.writeln(';') + return + } if right_sym.info is ast.FnType && is_decl { if is_inside_ternary { g.out.write_string(util.tabs(g.indent - g.inside_ternary)) diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index a5ef7c97e..19be2396e 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -255,7 +255,9 @@ mut: curr_var_name []string // curr var name on assignment called_fn_name string timers &util.Timers = util.get_timers() - force_main_console bool // true when @[console] used on fn main() + force_main_console bool // true when @[console] used on fn main() + uses_power bool + uses_power_u64 bool as_cast_type_names map[string]string // table for type name lookup in runtime (for __as_cast) obf_table map[string]string referenced_fns shared map[string]bool // functions that have been referenced @@ -442,6 +444,8 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO global_g.export_funcs << g.export_funcs global_g.force_main_console = global_g.force_main_console || g.force_main_console + global_g.uses_power = global_g.uses_power || g.uses_power + global_g.uses_power_u64 = global_g.uses_power_u64 || g.uses_power_u64 // merge maps for k, v in g.vsafe_arithmetic_ops { @@ -748,6 +752,48 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO } } } + if g.uses_power { + b.writeln('#include ') + b.writeln('static inline i64 __v_pow_i64(i64 base, i64 exponent) {') + b.writeln('\tif (exponent < 0) {') + b.writeln('\t\tif (base == 0) {') + b.writeln('\t\t\treturn -1;') + b.writeln('\t\t}') + b.writeln('\t\tif (base != 1 && base != -1) {') + b.writeln('\t\t\treturn 0;') + b.writeln('\t\t}') + b.writeln('\t\treturn (exponent & 1) != 0 ? base : 1;') + b.writeln('\t}') + b.writeln('\ti64 value = 1;') + b.writeln('\ti64 power = base;') + b.writeln('\tfor (; exponent > 0; exponent >>= 1) {') + b.writeln('\t\tif ((exponent & 1) != 0) {') + b.writeln('\t\t\tvalue *= power;') + b.writeln('\t\t}') + b.writeln('\t\tpower *= power;') + b.writeln('\t}') + b.writeln('\treturn value;') + b.writeln('}') + } + if g.uses_power_u64 { + b.writeln('static inline u64 __v_pow_u64(u64 base, i64 exponent) {') + b.writeln('\tif (exponent < 0) {') + b.writeln('\t\tif (base == 0) {') + b.writeln('\t\t\treturn ((u64)-1);') + b.writeln('\t\t}') + b.writeln('\t\treturn base == 1 ? 1 : 0;') + b.writeln('\t}') + b.writeln('\tu64 value = 1;') + b.writeln('\tu64 power = base;') + b.writeln('\tfor (; exponent > 0; exponent >>= 1) {') + b.writeln('\t\tif ((exponent & 1) != 0) {') + b.writeln('\t\t\tvalue *= power;') + b.writeln('\t\t}') + b.writeln('\t\tpower *= power;') + b.writeln('\t}') + b.writeln('\treturn value;') + b.writeln('}') + } if g.pref.is_coverage { b.write_string2('\n// V coverage:\n', g.cov_declarations.str()) } diff --git a/vlib/v/gen/c/fn.v b/vlib/v/gen/c/fn.v index bf8848b5a..61afa6aac 100644 --- a/vlib/v/gen/c/fn.v +++ b/vlib/v/gen/c/fn.v @@ -1245,7 +1245,7 @@ fn (mut g Gen) gen_fn_decl(node &ast.FnDecl, skip bool) { fn (mut g Gen) c_fn_name(node &ast.FnDecl) string { mut name := node.name - if name in ['+', '-', '*', '/', '%', '<', '=='] { + if name in ['+', '-', '*', '**', '/', '%', '<', '=='] { name = util.replace_op(name) } if node.is_method { diff --git a/vlib/v/gen/c/infix.v b/vlib/v/gen/c/infix.v index 9b4d0a6dc..262380e8e 100644 --- a/vlib/v/gen/c/infix.v +++ b/vlib/v/gen/c/infix.v @@ -54,7 +54,7 @@ fn (mut g Gen) infix_expr(node ast.InfixExpr) { .key_is, .not_is { g.infix_expr_is_op(node) } - .plus, .minus, .mul, .div, .mod { + .plus, .minus, .mul, .power, .div, .mod { g.infix_expr_arithmetic_op(node) } .left_shift { @@ -1732,6 +1732,69 @@ struct VSafeArithmeticOp { op token.Kind } +fn (mut g Gen) normalized_power_result_type(result_type ast.Type, left_type ast.Type, right_type ast.Type) ast.Type { + mut typ := g.unwrap_generic(g.recheck_concrete_type(result_type)).clear_flag(.shared_f).clear_flag(.atomic_f) + if typ == 0 || typ == ast.void_type { + typ = g.unwrap_generic(g.type_resolver.promote_type(g.unwrap_generic(left_type), + g.unwrap_generic(right_type))).clear_flag(.shared_f).clear_flag(.atomic_f) + } + if typ == ast.int_literal_type { + if left_type !in [ast.int_literal_type, ast.float_literal_type] { + typ = g.unwrap_generic(left_type) + } else if right_type !in [ast.int_literal_type, ast.float_literal_type] { + typ = g.unwrap_generic(right_type) + } else { + typ = ast.int_type + } + } else if typ == ast.float_literal_type { + if left_type !in [ast.int_literal_type, ast.float_literal_type] { + typ = g.unwrap_generic(left_type) + } else if right_type !in [ast.int_literal_type, ast.float_literal_type] { + typ = g.unwrap_generic(right_type) + } else { + typ = ast.f64_type + } + } + return typ.clear_flag(.shared_f).clear_flag(.atomic_f) +} + +fn (mut g Gen) gen_power_expr_from_types(left ast.Expr, left_type ast.Type, right ast.Expr, right_type ast.Type, result_type ast.Type) { + power_result_type := g.normalized_power_result_type(result_type, left_type, right_type) + builtin_power_type := g.table.unalias_num_type(power_result_type) + result_styp := g.styp(power_result_type) + g.uses_power = true + if builtin_power_type == ast.f32_type { + g.write('(${result_styp})powf(') + g.expr_with_cast(left, left_type, ast.f32_type) + g.write(', ') + g.expr_with_cast(right, right_type, ast.f32_type) + g.write(')') + return + } + if builtin_power_type.is_float() { + g.write('(${result_styp})pow(') + g.expr_with_cast(left, left_type, ast.f64_type) + g.write(', ') + g.expr_with_cast(right, right_type, ast.f64_type) + g.write(')') + return + } + if builtin_power_type.is_unsigned() { + g.uses_power_u64 = true + g.write('(${result_styp})__v_pow_u64(') + g.expr_with_cast(left, left_type, ast.u64_type) + g.write(', ') + g.expr_with_cast(right, right_type, ast.i64_type) + g.write(')') + return + } + g.write('(${result_styp})__v_pow_i64(') + g.expr_with_cast(left, left_type, ast.i64_type) + g.write(', ') + g.expr_with_cast(right, right_type, ast.i64_type) + g.write(')') +} + // gen_plain_infix_expr generates basic code for infix expressions, // without any overloading of any kind // i.e. v`a + 1` => c`a + 1` @@ -1764,6 +1827,17 @@ fn (mut g Gen) gen_plain_infix_expr(node ast.InfixExpr) { resolved_right_type = node.right_type } } + if node.op == .power { + power_left_type := if resolved_left_type != 0 { resolved_left_type } else { node.left_type } + power_right_type := if resolved_right_type != 0 { + resolved_right_type + } else { + node.right_type + } + g.gen_power_expr_from_types(node.left, power_left_type, node.right, power_right_type, + node.promoted_type) + return + } $if trace_ci_fixes ? { if g.file.path.contains('binary_search_tree.v') && node.right is ast.SelectorExpr { if node.right.expr is ast.Ident && node.right.expr.name == 'tree' { diff --git a/vlib/v/gen/c/utils.v b/vlib/v/gen/c/utils.v index a7931970f..cb8c161a2 100644 --- a/vlib/v/gen/c/utils.v +++ b/vlib/v/gen/c/utils.v @@ -880,7 +880,7 @@ fn (mut g Gen) resolved_expr_type(expr ast.Expr, default_typ ast.Type) ast.Type right_default := if expr.right_type != 0 { expr.right_type } else { default_typ } left_type := g.resolved_expr_type(expr.left, left_default) right_type := g.resolved_expr_type(expr.right, right_default) - if expr.op in [.plus, .minus, .mul, .div, .mod] { + if expr.op in [.plus, .minus, .mul, .power, .div, .mod] { if left_type == right_type && left_type != 0 && left_type !in [ast.int_literal_type, ast.float_literal_type] { return g.unwrap_generic(left_type) diff --git a/vlib/v/gen/js/fn.v b/vlib/v/gen/js/fn.v index 4a99e6184..2d4cbe0b1 100644 --- a/vlib/v/gen/js/fn.v +++ b/vlib/v/gen/js/fn.v @@ -626,7 +626,7 @@ fn (mut g JsGen) gen_method_decl(it ast.FnDecl, typ FnGenType) { g.table.cur_fn = &it } mut name := it.name - if name in ['+', '-', '*', '/', '%', '<', '=='] { + if name in ['+', '-', '*', '**', '/', '%', '<', '=='] { name = util.replace_op(name) } @@ -799,7 +799,7 @@ fn (mut g JsGen) gen_anon_fn(mut fun ast.AnonFn) { g.table.cur_fn = &it } mut name := it.name - if name in ['+', '-', '*', '/', '%', '<', '=='] { + if name in ['+', '-', '*', '**', '/', '%', '<', '=='] { name = util.replace_op(name) } g.writeln('(function () { ') diff --git a/vlib/v/gen/js/infix.v b/vlib/v/gen/js/infix.v index 0663942b8..500039fb1 100644 --- a/vlib/v/gen/js/infix.v +++ b/vlib/v/gen/js/infix.v @@ -11,6 +11,29 @@ fn (mut g JsGen) gen_plain_infix_expr(node ast.InfixExpr) { cast_ty := if greater_typ == it.left_type { l_sym.cname } else { r_sym.cname } g.write('new ${g.js_name(cast_ty)}( ') g.cast_stack << greater_typ + if node.op == .power { + if !g.pref.output_es5 && ((l_sym.kind == .i64 || l_sym.kind == .u64) + || (r_sym.kind == .i64 || r_sym.kind == .u64)) { + g.write('BigInt(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf()) ** BigInt(') + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + g.write('.valueOf())') + } else { + g.write('Math.pow(') + g.expr(node.left) + g.gen_deref_ptr(node.left_type) + g.write('.valueOf(), ') + g.expr(node.right) + g.gen_deref_ptr(node.right_type) + g.write('.valueOf())') + } + g.cast_stack.delete_last() + g.write(')') + return + } if !g.pref.output_es5 && ((l_sym.kind == .i64 || l_sym.kind == .u64) || (r_sym.kind == .i64 || r_sym.kind == .u64)) { g.write('BigInt(') @@ -362,7 +385,7 @@ fn (mut g JsGen) infix_is_not_is_op(node ast.InfixExpr) { fn (mut g JsGen) infix_expr(node ast.InfixExpr) { match node.op { - .plus, .minus, .mul, .div, .mod { + .plus, .minus, .mul, .power, .div, .mod { g.infix_expr_arithmetic_op(node) } .eq, .ne { diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index 9096d05be..df8775aa7 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -1377,6 +1377,73 @@ fn (mut g JsGen) gen_assign_stmt(stmt ast.AssignStmt, semicolon bool) { } else { g.expr(left) } + if stmt.op == .power_assign { + left_info := g.unwrap(stmt.left_types[i]) + right_info := g.unwrap(stmt.right_types[i]) + if method := g.table.find_method(left_info.sym, '**') { + if !array_set { + g.write(' = ') + } + left_styp := g.styp(left_info.typ.set_nr_muls(0)) + g.write('${left_styp}_${util.replace_op('**')}(') + g.op_arg(left, method.params[0].typ, left_info.typ) + g.write(', ') + g.op_arg(val, method.params[1].typ, right_info.typ) + g.write(')') + if array_set && !map_set { + g.write(')') + } + if map_set { + g.write('}') + } + if semicolon { + g.writeln(';') + } + continue + } + } + if stmt.op == .power_assign { + left_sym := g.table.final_sym(stmt.left_types[i]) + if !array_set { + g.write('.val = ') + } + g.write('new ${styp}(') + needs_floor := left_sym.name !in ['f32', 'f64', 'i64', 'u64'] + if needs_floor { + g.write('Math.floor(') + } + if !g.pref.output_es5 && (left_sym.kind == .i64 || left_sym.kind == .u64) { + g.write('BigInt(') + g.expr(left) + g.gen_deref_ptr(stmt.left_types[i]) + g.write('.valueOf()) ** BigInt(') + g.expr(val) + g.gen_deref_ptr(stmt.right_types[i]) + g.write('.valueOf())') + } else { + g.write('Math.pow(') + g.expr(left) + g.gen_deref_ptr(stmt.left_types[i]) + g.write('.valueOf(), ') + g.expr(val) + g.gen_deref_ptr(stmt.right_types[i]) + g.write('.valueOf())') + } + if needs_floor { + g.write(')') + } + g.write(')') + if array_set && !map_set { + g.write(')') + } + if map_set { + g.write('}') + } + if semicolon { + g.writeln(';') + } + continue + } is_ptr := stmt.op == .assign && stmt.right_types[i].is_ptr() && !array_set if is_ptr { @@ -3080,8 +3147,8 @@ fn (mut g JsGen) gen_infix_expr(it ast.InfixExpr) { if is_not { g.write('!(') } - is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .div, .mod, .right_shift, .left_shift, - .amp, .pipe, .xor] + is_arithmetic := it.op in [token.Kind.plus, .minus, .mul, .power, .div, .mod, .right_shift, + .left_shift, .amp, .pipe, .xor] if !g.pref.output_es5 && is_arithmetic && ((l_sym.kind == .i64 || l_sym.kind == .u64) || (r_sym.kind == .i64 || r_sym.kind == .u64)) { @@ -3823,6 +3890,7 @@ fn replace_op(s string) string { '+' { '_plus' } '-' { '_minus' } '*' { '_mult' } + '**' { '_pow' } '/' { '_div' } '%' { '_mod' } '<' { '_lt' } diff --git a/vlib/v/parser/assign.v b/vlib/v/parser/assign.v index da973db73..643e8479c 100644 --- a/vlib/v/parser/assign.v +++ b/vlib/v/parser/assign.v @@ -188,6 +188,9 @@ fn (mut p Parser) check_cross_variables(exprs []ast.Expr, val ast.Expr) bool { fn (mut p Parser) partial_assign_stmt(left []ast.Expr) ast.Stmt { p.is_stmt_ident = false op := p.tok.kind + if op == .power_assign { + p.register_auto_import('math') + } mut pos := p.tok.pos() p.next() mut right := []ast.Expr{cap: left.len} diff --git a/vlib/v/parser/expr.v b/vlib/v/parser/expr.v index 69a0b2b8a..ae067b639 100644 --- a/vlib/v/parser/expr.v +++ b/vlib/v/parser/expr.v @@ -173,9 +173,13 @@ fn (mut p Parser) check_expr(precedence int) !ast.Expr { // &x, *x, !x, ~x, <-x node = p.prefix_expr() } + .power { + node = p.power_prefix_expr() + } .minus { // -1, -a - if p.peek_tok.kind == .number { + if p.peek_tok.kind == .number && !(p.peek_token(2).kind == .power + && p.peek_token(2).line_nr == p.tok.line_nr) { node = p.parse_number_literal() } else { node = p.prefix_expr() @@ -856,6 +860,8 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { if op == .arrow { p.or_is_handled = true p.register_auto_import('sync') + } else if op == .power { + p.register_auto_import('math') } precedence := p.tok.kind.precedence() mut pos := p.tok.pos() @@ -885,12 +891,13 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { if op in [.decl_assign, .assign] { p.inside_assign_rhs = true } - right = p.expr(precedence) + right = p.expr(if op == .power { precedence - 1 } else { precedence }) p.inside_assign_rhs = old_assign_rhs - if op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && mut right is ast.PrefixExpr { + if op in [.plus, .minus, .mul, .power, .div, .mod, .lt, .eq] && mut right is ast.PrefixExpr { mut right_expr := right.right right_expr = right_expr.remove_par() - if right.op in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && right_expr.is_pure_literal() { + if right.op in [.plus, .minus, .mul, .power, .div, .mod, .lt, .eq] + && right_expr.is_pure_literal() { p.error_with_pos('invalid expression: unexpected token `${op}`', right_op_pos) } } @@ -1049,6 +1056,20 @@ fn (mut p Parser) prefix_expr() ast.Expr { } } +fn (mut p Parser) power_prefix_expr() ast.Expr { + pos := p.tok.pos() + p.next() + mut right := p.expr(int(token.Precedence.prefix)) + for _ in 0 .. 2 { + right = ast.PrefixExpr{ + op: .mul + right: right + pos: pos + } + } + return right +} + fn (mut p Parser) recast_as_pointer(mut cast_expr ast.CastExpr, pos token.Pos) { cast_expr.typ = cast_expr.typ.ref() cast_expr.typname = if cast_expr.typ == 0 { diff --git a/vlib/v/parser/fn.v b/vlib/v/parser/fn.v index c9760147d..130756977 100644 --- a/vlib/v/parser/fn.v +++ b/vlib/v/parser/fn.v @@ -728,7 +728,8 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } } } - } else if p.tok.kind in [.plus, .minus, .mul, .div, .mod, .lt, .eq] && p.peek_tok.kind == .lpar { + } else if p.tok.kind in [.plus, .minus, .mul, .power, .div, .mod, .lt, .eq] + && p.peek_tok.kind == .lpar { name = p.tok.kind.str() // op_to_fn_name() if rec.typ == ast.void_type { p.error_with_pos('cannot use operator overloading with normal functions', p.tok.pos()) @@ -740,13 +741,15 @@ fn (mut p Parser) fn_decl() ast.FnDecl { } else if p.tok.kind in [.ne, .gt, .ge, .le] && p.peek_tok.kind == .lpar { p.error_with_pos('cannot overload `!=`, `>`, `<=` and `>=` as they are auto generated from `==` and`<`', p.tok.pos()) - } else if p.tok.kind in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .mod_assign] { + } else if p.tok.kind in [.plus_assign, .minus_assign, .div_assign, .mult_assign, .power_assign, + .mod_assign] { extracted_op := match p.tok.kind { .plus_assign { '+' } .minus_assign { '-' } .div_assign { '/' } .mod_assign { '%' } .mult_assign { '*' } + .power_assign { '**' } else { 'unknown op' } } if type_sym.has_method(extracted_op) { diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index 2b1debeb3..879c64826 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -788,6 +788,14 @@ pub fn (mut s Scanner) text_scan() token.Token { return s.new_token(.minus, '', 1) } `*` { + if nextc == `*` { + if s.look_ahead(2) == `=` { + s.pos += 2 + return s.new_token(.power_assign, '', 3) + } + s.pos++ + return s.new_token(.power, '', 2) + } if nextc == `=` { s.pos++ return s.new_token(.mult_assign, '', 2) diff --git a/vlib/v/tests/power_operator_test.v b/vlib/v/tests/power_operator_test.v new file mode 100644 index 000000000..ee8183cfc --- /dev/null +++ b/vlib/v/tests/power_operator_test.v @@ -0,0 +1,48 @@ +import math + +struct Exponent { + value int +} + +fn (a Exponent) **(b Exponent) Exponent { + mut result := 1 + for _ in 0 .. b.value { + result *= a.value + } + return Exponent{ + value: result + } +} + +const const_power = 2 ** 5 + +fn test_power_operator_with_ints() { + assert const_power == 32 + assert 2 ** 3 == 8 + assert 2 ** 3 ** 2 == 512 + assert -2 ** 2 == -4 + assert (-2) ** 2 == 4 +} + +fn test_power_assign_with_ints() { + mut value := 3 + value **= 3 + assert value == 27 +} + +fn test_power_operator_with_floats() { + assert math.abs(9.0 ** 0.5 - 3.0) < 1e-9 +} + +fn test_power_operator_overload() { + mut value := Exponent{ + value: 2 + } + assert (value ** Exponent{ + value: 3 + }).value == 8 + value **= Exponent{ + value: 3 + } + assert value.value == 8 +} diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index a7ae53c8b..67084bdca 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -29,6 +29,7 @@ pub enum Kind { plus // + minus // - mul // * + power // ** div // / mod // % xor // ^ @@ -60,6 +61,7 @@ pub enum Kind { minus_assign // -= div_assign // /= mult_assign // *= + power_assign // **= xor_assign // ^= mod_assign // %= or_assign // |= @@ -196,8 +198,9 @@ pub enum AtKind { } pub const assign_tokens = [Kind.assign, .decl_assign, .plus_assign, .minus_assign, .mult_assign, - .div_assign, .xor_assign, .mod_assign, .or_assign, .and_assign, .right_shift_assign, - .left_shift_assign, .unsigned_right_shift_assign, .boolean_and_assign, .boolean_or_assign] + .power_assign, .div_assign, .xor_assign, .mod_assign, .or_assign, .and_assign, + .right_shift_assign, .left_shift_assign, .unsigned_right_shift_assign, .boolean_and_assign, + .boolean_or_assign] pub const valid_at_tokens = ['@VROOT', '@VMODROOT', '@VEXEROOT', '@FN', '@METHOD', '@MOD', '@STRUCT', '@VEXE', '@FILE', '@DIR', '@LINE', '@COLUMN', '@VHASH', '@VCURRENTHASH', '@VMOD_FILE', @@ -241,6 +244,7 @@ fn build_token_str() []string { s[Kind.plus] = '+' s[Kind.minus] = '-' s[Kind.mul] = '*' + s[Kind.power] = '**' s[Kind.div] = '/' s[Kind.mod] = '%' s[Kind.xor] = '^' @@ -267,6 +271,7 @@ fn build_token_str() []string { s[Kind.plus_assign] = '+=' s[Kind.minus_assign] = '-=' s[Kind.mult_assign] = '*=' + s[Kind.power_assign] = '**=' s[Kind.div_assign] = '/=' s[Kind.xor_assign] = '^=' s[Kind.mod_assign] = '%=' @@ -439,6 +444,7 @@ pub enum Precedence { product // * / << >> >>> & // mod // % prefix // -X or !X; TODO: seems unused + power // ** postfix // ++ or -- call // func(X) or foo.method(X) index // array[index], map[key] @@ -464,6 +470,7 @@ pub fn build_precedences() []Precedence { p[Kind.unsigned_right_shift] = .product p[Kind.amp] = .product p[Kind.arrow] = .product + p[Kind.power] = .power // `+` | `-` | `|` | `^` p[Kind.plus] = .sum p[Kind.minus] = .sum @@ -482,6 +489,7 @@ pub fn build_precedences() []Precedence { p[Kind.assign] = .assign p[Kind.plus_assign] = .assign p[Kind.minus_assign] = .assign + p[Kind.power_assign] = .assign p[Kind.div_assign] = .assign p[Kind.mod_assign] = .assign p[Kind.or_assign] = .assign @@ -549,8 +557,8 @@ pub fn (kind Kind) is_prefix() bool { @[inline] pub fn (kind Kind) is_infix() bool { - return kind in [.plus, .minus, .mod, .mul, .div, .eq, .ne, .gt, .lt, .key_in, .key_as, .ge, - .le, .logical_or, .xor, .not_in, .key_is, .not_is, .and, .dot, .pipe, .amp, .left_shift, + return kind in [.plus, .minus, .mod, .mul, .power, .div, .eq, .ne, .gt, .lt, .key_in, .key_as, + .ge, .le, .logical_or, .xor, .not_in, .key_is, .not_is, .and, .dot, .pipe, .amp, .left_shift, .right_shift, .unsigned_right_shift, .arrow, .key_like, .key_ilike] } @@ -571,6 +579,7 @@ pub fn kind_to_string(k Kind) string { .plus { 'plus' } .minus { 'minus' } .mul { 'mul' } + .power { 'power' } .div { 'div' } .mod { 'mod' } .xor { 'xor' } @@ -602,6 +611,7 @@ pub fn kind_to_string(k Kind) string { .minus_assign { 'minus_assign' } .div_assign { 'div_assign' } .mult_assign { 'mult_assign' } + .power_assign { 'power_assign' } .xor_assign { 'xor_assign' } .mod_assign { 'mod_assign' } .or_assign { 'or_assign' } @@ -691,6 +701,7 @@ pub fn assign_op_to_infix_op(op Kind) Kind { .plus_assign { .plus } .minus_assign { .minus } .mult_assign { .mul } + .power_assign { .power } .div_assign { .div } .xor_assign { .xor } .mod_assign { .mod } diff --git a/vlib/v/transformer/transformer.v b/vlib/v/transformer/transformer.v index e5cbd5c7c..fc7f883df 100644 --- a/vlib/v/transformer/transformer.v +++ b/vlib/v/transformer/transformer.v @@ -902,6 +902,12 @@ pub fn (mut t Transformer) infix_expr(mut node ast.InfixExpr) ast.Expr { pos: pos } } + .power { + return ast.IntegerLiteral{ + val: math.powi(left_val, right_val).str() + pos: pos + } + } .minus { // HACK: prevent folding of `min_i64` values in `math` module if left_val == -9223372036854775807 && right_val == 1 { @@ -1009,6 +1015,12 @@ pub fn (mut t Transformer) infix_expr(mut node ast.InfixExpr) ast.Expr { .mul { return folded_float_literal(left_val * right_val, pos) } + .power { + return ast.FloatLiteral{ + val: math.pow(left_val, right_val).str() + pos: pos + } + } .minus { return folded_float_literal(left_val - right_val, pos) } diff --git a/vlib/v/type_resolver/type_resolver.v b/vlib/v/type_resolver/type_resolver.v index 552533707..a787d94b9 100644 --- a/vlib/v/type_resolver/type_resolver.v +++ b/vlib/v/type_resolver/type_resolver.v @@ -214,10 +214,10 @@ pub fn (mut t TypeResolver) get_type_or_default(node ast.Expr, default_typ ast.T return t.get_type_or_default(node.expr, default_typ) } ast.InfixExpr { - if !node.left.is_literal() && node.op in [.plus, .minus, .mul, .div, .mod] { + if !node.left.is_literal() && node.op in [.plus, .minus, .mul, .power, .div, .mod] { return t.get_type_or_default(node.left, default_typ) } - if !node.right.is_literal() && node.op in [.plus, .minus, .mul, .div, .mod] { + if !node.right.is_literal() && node.op in [.plus, .minus, .mul, .power, .div, .mod] { return t.get_type_or_default(node.right, default_typ) } } diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index fbc1a6620..6df1bfdfa 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -382,6 +382,7 @@ pub fn replace_op(s string) string { '+' { '_plus' } '-' { '_minus' } '*' { '_mult' } + '**' { '_pow' } '/' { '_div' } '%' { '_mod' } '<' { '_lt' } -- 2.39.5