From 537fa1bb6d9799ba198fd8bbf6d90e3558228d9a Mon Sep 17 00:00:00 2001 From: larpon <768942+larpon@users.noreply.github.com> Date: Thu, 20 Jun 2024 11:33:23 +0200 Subject: [PATCH] comptime, ast: support `[$d('s', 4)]int{}`, move resolving to method on `ComptimeCall` (#21701) --- vlib/v/ast/ast.v | 60 +++++++++++++++++++ vlib/v/checker/comptime.v | 53 +--------------- vlib/v/checker/containers.v | 14 +++++ ...lue_d_values_can_only_be_pure_literals.out | 4 +- .../tests/run/using_comptime_d_value.run.out | 1 + .../tests/run/using_comptime_d_value.vv | 2 + .../c/testdata/use_flag_comptime_values.out | 1 + .../c/testdata/use_flag_comptime_values.vv | 5 +- vlib/v/parser/parse_type.v | 13 ++++ vlib/v/scanner/scanner.v | 10 +--- vlib/v/tests/comptime_value_d_default_test.v | 2 + vlib/v/util/util.v | 7 +++ 12 files changed, 110 insertions(+), 62 deletions(-) diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 9f1746b6d..6f0b6da3b 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -5,6 +5,7 @@ module ast import v.token import v.errors +import v.util import v.pref pub type TypeDecl = AliasTypeDecl | FnTypeDecl | SumTypeDecl @@ -1924,6 +1925,8 @@ pub: is_compile_value bool // $d(...) env_pos token.Pos is_pkgconfig bool +mut: + is_d_resolved bool pub mut: vweb_tmpl File left Expr @@ -1937,6 +1940,31 @@ pub mut: or_block OrExpr } +// resolve_compile_value resolves the value and return type of `$d()` calls. +// The result is stored in fields `compile_value` and `result_type`. +// The argument `compile_values` is expected to be the `Preferences.compile_values` field. +pub fn (mut cc ComptimeCall) resolve_compile_value(compile_values map[string]string) ! { + if cc.is_d_resolved { + return + } + if !cc.is_compile_value { + return error('ComptimeCall is not \$d()') + } + arg := cc.args[0] or { + return error('\$d() takes two arguments, a string and a primitive literal') + } + if !arg.expr.is_pure_literal() { + return error('\$d() values can only be pure literals') + } + typ := arg.expr.get_pure_type() + arg_as_string := arg.str().trim('`"\'') + value := compile_values[cc.args_var] or { arg_as_string } + validate_type_string_is_pure_literal(typ, value) or { return error(err.msg()) } + cc.compile_value = value + cc.result_type = typ + cc.is_d_resolved = true +} + pub struct None { pub: pos token.Pos @@ -2530,3 +2558,35 @@ pub fn type_can_start_with_token(tok &token.Token) bool { } } } + +// validate_type_string_is_pure_literal returns `Error` if `str` can not be converted +// to pure literal `typ` (`i64`, `f64`, `bool`, `char` or `string`). +pub fn validate_type_string_is_pure_literal(typ Type, str string) ! { + if typ == bool_type { + if !(str == 'true' || str == 'false') { + return error('bool literal `true` or `false` expected, found "${str}"') + } + } else if typ == char_type { + if str.starts_with('\\') { + if str.len <= 1 { + return error('empty escape sequence found') + } + if !util.is_escape_sequence(str[1]) { + return error('char literal escape sequence expected, found "${str}"') + } + } else if str.len != 1 { + return error('char literal expected, found "${str}"') + } + } else if typ == f64_type { + if str.count('.') != 1 { + return error('f64 literal expected, found "${str}"') + } + } else if typ == string_type { + } else if typ == i64_type { + if !str.is_int() { + return error('i64 literal expected, found "${str}"') + } + } else { + return error('expected pure literal, found "${str}"') + } +} diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index c7ba81757..2c7b3e8ae 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -30,24 +30,11 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { return ast.string_type } if node.is_compile_value { - arg := node.args[0] or { - c.error('\$d() takes two arguments, a string and a primitive literal', node.pos) - return ast.void_type - } - if !arg.expr.is_pure_literal() { - c.error('-d values can only be pure literals', node.pos) - return ast.void_type - } - typ := arg.expr.get_pure_type() - arg_as_string := arg.str().trim('`"\'') - value := c.pref.compile_values[node.args_var] or { arg_as_string } - validate_type_string_is_pure_literal(typ, value) or { + node.resolve_compile_value(c.pref.compile_values) or { c.error(err.msg(), node.pos) return ast.void_type } - node.compile_value = value - node.result_type = typ - return typ + return node.result_type } if node.is_embed { if node.args.len == 1 { @@ -589,42 +576,6 @@ fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.Comp return none } -fn validate_type_string_is_pure_literal(typ ast.Type, str string) ! { - if typ == ast.bool_type { - if !(str == 'true' || str == 'false') { - return error('bool literal `true` or `false` expected, found "${str}"') - } - } else if typ == ast.char_type { - if str.starts_with('\\') { - if str.len <= 1 { - return error('empty escape sequence found') - } - if !is_escape_sequence(str[1]) { - return error('char literal escape sequence expected, found "${str}"') - } - } else if str.len != 1 { - return error('char literal expected, found "${str}"') - } - } else if typ == ast.f64_type { - if str.count('.') != 1 { - return error('f64 literal expected, found "${str}"') - } - } else if typ == ast.string_type { - } else if typ == ast.i64_type { - if !str.is_int() { - return error('i64 literal expected, found "${str}"') - } - } else { - return error('expected pure literal, found "${str}"') - } -} - -@[inline] -fn is_escape_sequence(c u8) bool { - return c in [`x`, `u`, `e`, `n`, `r`, `t`, `v`, `a`, `f`, `b`, `\\`, `\``, `$`, `@`, `?`, `{`, - `}`, `'`, `"`, `U`] -} - fn (mut c Checker) verify_vweb_params_for_method(node ast.Fn) (bool, int, int) { margs := node.params.len - 1 // first arg is the receiver/this // if node.attrs.len == 0 || (node.attrs.len == 1 && node.attrs[0].name == 'post') { diff --git a/vlib/v/checker/containers.v b/vlib/v/checker/containers.v index 6c12e38c6..06c85140d 100644 --- a/vlib/v/checker/containers.v +++ b/vlib/v/checker/containers.v @@ -308,6 +308,20 @@ fn (mut c Checker) eval_array_fixed_sizes(mut size_expr ast.Expr, size int, elem ast.IntegerLiteral { fixed_size = size_expr.val.int() } + ast.ComptimeCall { + if size_expr.is_compile_value { + size_expr.resolve_compile_value(c.pref.compile_values) or { + c.error(err.msg(), size_expr.pos) + } + if size_expr.result_type != ast.i64_type { + c.error('value from \$d() can only be positive integers when used as fixed size', + size_expr.pos) + } + fixed_size = size_expr.compile_value.int() + } else { + c.error('only \$d() can be used for fixed size arrays', size_expr.pos) + } + } ast.CastExpr { if !size_expr.typ.is_pure_int() { c.error('only integer types are allowed', size_expr.pos) diff --git a/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.out b/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.out index 17bb7e081..2fb919a1b 100644 --- a/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.out +++ b/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.out @@ -1,3 +1,3 @@ -vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.vv:1:16: error: -d values can only be pure literals +vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.vv:1:16: error: $d() values can only be pure literals 1 | const my_f32 = $d('my_f32', f32(42.0)) - | ~~~~~~~~~~~~~~~~~~~~~~~ + | ~~~~~~~~~~~~~~~~~~~~~~~ \ No newline at end of file diff --git a/vlib/v/checker/tests/run/using_comptime_d_value.run.out b/vlib/v/checker/tests/run/using_comptime_d_value.run.out index d1e104b41..a73aabbfb 100644 --- a/vlib/v/checker/tests/run/using_comptime_d_value.run.out +++ b/vlib/v/checker/tests/run/using_comptime_d_value.run.out @@ -1,3 +1,4 @@ 42.0 false done +[0, 0, 0, 0] diff --git a/vlib/v/checker/tests/run/using_comptime_d_value.vv b/vlib/v/checker/tests/run/using_comptime_d_value.vv index 75725a794..c0b8b47e7 100644 --- a/vlib/v/checker/tests/run/using_comptime_d_value.vv +++ b/vlib/v/checker/tests/run/using_comptime_d_value.vv @@ -8,4 +8,6 @@ fn main() { cv_bool := $d('my_bool', false) println(cv_bool) println('done') + fsa := [$d('fixed_size', 4)]int{} + println(fsa) } diff --git a/vlib/v/gen/c/testdata/use_flag_comptime_values.out b/vlib/v/gen/c/testdata/use_flag_comptime_values.out index d343dc635..15e0c2d8d 100644 --- a/vlib/v/gen/c/testdata/use_flag_comptime_values.out +++ b/vlib/v/gen/c/testdata/use_flag_comptime_values.out @@ -3,3 +3,4 @@ a four true g +[0, 0, 0, 0, 0, 0, 0, 0] diff --git a/vlib/v/gen/c/testdata/use_flag_comptime_values.vv b/vlib/v/gen/c/testdata/use_flag_comptime_values.vv index 7f17b3687..9adfcd321 100644 --- a/vlib/v/gen/c/testdata/use_flag_comptime_values.vv +++ b/vlib/v/gen/c/testdata/use_flag_comptime_values.vv @@ -1,5 +1,5 @@ // This file should pass if compiled/run with: -// vtest vflags: -d my_f64=2.0 -d my_i64=3 -d my_string="a four" -d my_bool -d my_char=g +// vtest vflags: -d my_f64=2.0 -d my_i64=3 -d my_string="a four" -d my_bool -d my_char=g -d my_size=8 const my_f64 = $d('my_f64', 1.0) const my_i64 = $d('my_i64', 2) const my_string = $d('my_string', 'three') @@ -12,9 +12,12 @@ fn main() { assert my_string == 'a four' assert my_bool == true assert my_char == `g` + my_fixed_size_array := [$d('my_size', 4)]int{} + assert my_fixed_size_array.len == 8 println(my_f64) println(my_i64) println(my_string) println(my_bool) println(rune(my_char)) + println(my_fixed_size_array) } diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index e386c7cc9..a63560160 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -25,6 +25,19 @@ fn (mut p Parser) parse_array_type(expecting token.Kind, is_option bool) ast.Typ fixed_size = size_expr.val.int() size_unresolved = false } + ast.ComptimeCall { + if size_expr.is_compile_value { + size_expr.resolve_compile_value(p.pref.compile_values) or { + p.error_with_pos(err.msg(), size_expr.pos) + } + if size_expr.result_type != ast.i64_type { + p.error_with_pos('value from \$d() can only be positive integers when used as fixed size', + size_expr.pos) + } + fixed_size = size_expr.compile_value.int() + size_unresolved = false + } + } ast.Ident { if mut const_field := p.table.global_scope.find_const('${p.mod}.${size_expr.name}') { if mut const_field.expr is ast.IntegerLiteral { diff --git a/vlib/v/scanner/scanner.v b/vlib/v/scanner/scanner.v index ff4f65c7b..ec0f2eda2 100644 --- a/vlib/v/scanner/scanner.v +++ b/vlib/v/scanner/scanner.v @@ -1297,7 +1297,7 @@ pub fn (mut s Scanner) ident_string() string { u32_escapes_pos << s.pos - 1 } // Unknown escape sequence - if !is_escape_sequence(c) && !c.is_digit() && c != `\n` { + if !util.is_escape_sequence(c) && !c.is_digit() && c != `\n` { s.error('`${c.ascii_str()}` unknown escape sequence') } } @@ -1517,12 +1517,6 @@ fn trim_slash_line_break(s string) string { return ret_str } -@[inline] -fn is_escape_sequence(c u8) bool { - return c in [`x`, `u`, `e`, `n`, `r`, `t`, `v`, `a`, `f`, `b`, `\\`, `\``, `$`, `@`, `?`, `{`, - `}`, `'`, `"`, `U`] -} - /// ident_char is called when a backtick "single-char" is parsed from the code /// it is needed because some runes (chars) are written with escape sequences /// the string it returns should be a standardized, simplified version of the character @@ -1643,7 +1637,7 @@ pub fn (mut s Scanner) ident_char() string { s.error_with_pos('invalid character literal, use \`\\n\` instead', lspos) } else if c.len > len { ch := c[c.len - 1] - if !is_escape_sequence(ch) && !ch.is_digit() { + if !util.is_escape_sequence(ch) && !ch.is_digit() { s.error('`${ch.ascii_str()}` unknown escape sequence') } } diff --git a/vlib/v/tests/comptime_value_d_default_test.v b/vlib/v/tests/comptime_value_d_default_test.v index a9927d896..8506ed86e 100644 --- a/vlib/v/tests/comptime_value_d_default_test.v +++ b/vlib/v/tests/comptime_value_d_default_test.v @@ -10,4 +10,6 @@ fn test_default_compile_values() { assert my_string == 'three' assert my_bool == false assert my_char == `f` + my_fixed_size_array := [$d('my_size', 4)]int{} + assert my_fixed_size_array.len == 4 } diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index edd14c1c0..bcab7ccf8 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -180,6 +180,13 @@ pub fn resolve_d_value(compile_values map[string]string, str string) !string { return rep } +// is_escape_sequence returns `true` if `c` is considered a valid escape sequence denoter. +@[inline] +pub fn is_escape_sequence(c u8) bool { + return c in [`x`, `u`, `e`, `n`, `r`, `t`, `v`, `a`, `f`, `b`, `\\`, `\``, `$`, `@`, `?`, `{`, + `}`, `'`, `"`, `U`] +} + // launch_tool - starts a V tool in a separate process, passing it the `args`. // All V tools are located in the cmd/tools folder, in files or folders prefixed by // the letter `v`, followed by the tool name, i.e. `cmd/tools/vdoc/` or `cmd/tools/vpm.v`. -- 2.39.5