diff --git a/CHANGELOG.md b/CHANGELOG.md index cd9df61a425845..86a5db489bc81b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## V 0.4.7 (NEXT) + +#### Improvements in the language +- comptime: add support for `-d ident=value` and retrieval in code via `$d('ident', )`. + ## V 0.4.6 *20 May 2024* diff --git a/cmd/tools/vast/vast.v b/cmd/tools/vast/vast.v index f5374757244ebe..21999af3a9d82d 100644 --- a/cmd/tools/vast/vast.v +++ b/cmd/tools/vast/vast.v @@ -1019,6 +1019,7 @@ fn (t Tree) comptime_call(node ast.ComptimeCall) &Node { obj.add_terse('result_type', t.type_node(node.result_type)) obj.add('scope', t.scope(node.scope)) obj.add_terse('env_value', t.string_node(node.env_value)) + obj.add_terse('compile_value', t.string_node(node.compile_value)) obj.add('pos', t.pos(node.pos)) obj.add_terse('args', t.array_node_call_arg(node.args)) obj.add_terse('or_block', t.or_expr(node.or_block)) diff --git a/doc/docs.md b/doc/docs.md index 3c3d4d9737ca43..3dfc96fa888549 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -6120,6 +6120,67 @@ V can bring in values at compile time from environment variables. `$env('ENV_VAR')` can also be used in top-level `#flag` and `#include` statements: `#flag linux -I $env('JAVA_HOME')/include`. +#### `$d` + +V can bring in values at compile time from `-d ident=value` flag defines, passed on +the command line to the compiler. You can also pass `-d ident`, which will have the +same meaning as passing `-d ident=true`. + +To get the value in your code, use: `$d('ident', default)`, where `default` +can be `false` for booleans, `0` or `123` for i64 numbers, `0.0` or `113.0` +for f64 numbers, `'a string'` for strings. + +When a flag is not provided via the command line, `$d()` will return the `default` +value provided as the *second* argument. + +```v +module main + +const my_i64 = $d('my_i64', 1024) + +fn main() { + compile_time_value := $d('my_string', 'V') + println(compile_time_value) + println(my_i64) +} +``` + +Running the above with `v run .` will output: +``` +V +1024 +``` + +Running the above with `v -d my_i64=4096 -d my_string="V rocks" run .` will output: +``` +V rocks +4096 +``` + +Here is an example of how to use the default values, which have to be *pure* literals: +```v +fn main() { + val_str := $d('id_str', 'value') // can be changed by providing `-d id_str="my id"` + val_f64 := $d('id_f64', 42.0) // can be changed by providing `-d id_f64=84.0` + val_i64 := $d('id_i64', 56) // can be changed by providing `-d id_i64=123` + val_bool := $d('id_bool', false) // can be changed by providing `-d id_bool=true` + val_char := $d('id_char', `f`) // can be changed by providing `-d id_char=v` + println(val_str) + println(val_f64) + println(val_i64) + println(val_bool) + println(rune(val_char)) +} +``` + +`$d('ident','value')` can also be used in top-level statements like `#flag` and `#include`: +`#flag linux -I $d('my_include','/usr')/include`. The default value for `$d` when used in these +statements should be literal `string`s. + +`$d('ident', false)` can also be used inside `$if $d('ident', false) {` statements, +granting you the ability to selectively turn on/off certain sections of code, at compile +time, without modifying your source code, or keeping different versions of it. + #### `$compile_error` and `$compile_warn` These two comptime functions are very useful for displaying custom errors/warnings during diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 27308b32ac4c17..9f1746b6d4108c 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -1913,26 +1913,28 @@ pub mut: @[minify] pub struct ComptimeCall { pub: - pos token.Pos - has_parens bool // if $() is used, for vfmt - method_name string - method_pos token.Pos - scope &Scope = unsafe { nil } - is_vweb bool - is_embed bool - is_env bool - env_pos token.Pos - is_pkgconfig bool -pub mut: - vweb_tmpl File - left Expr - left_type Type - result_type Type - env_value string - args_var string - args []CallArg - embed_file EmbeddedFile - or_block OrExpr + pos token.Pos + has_parens bool // if $() is used, for vfmt + method_name string + method_pos token.Pos + scope &Scope = unsafe { nil } + is_vweb bool + is_embed bool // $embed_file(...) + is_env bool // $env(...) // TODO: deprecate after $d() is stable + is_compile_value bool // $d(...) + env_pos token.Pos + is_pkgconfig bool +pub mut: + vweb_tmpl File + left Expr + left_type Type + result_type Type + env_value string + compile_value string + args_var string + args []CallArg + embed_file EmbeddedFile + or_block OrExpr } pub struct None { diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 0eb5fff545a1ab..2021125b98279d 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -2428,6 +2428,13 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { } node.main = env } + if flag.contains('\$d(') { + d := util.resolve_d_value(c.pref.compile_values, flag) or { + c.error(err.msg(), node.pos) + return + } + node.main = d + } flag_no_comment := flag.all_before('//').trim_space() if node.kind == 'include' || node.kind == 'preinclude' { if !((flag_no_comment.starts_with('"') && flag_no_comment.ends_with('"')) @@ -2511,6 +2518,12 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { return } } + if flag.contains('\$d(') { + flag = util.resolve_d_value(c.pref.compile_values, flag) or { + c.error(err.msg(), node.pos) + return + } + } for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] { if flag.contains(deprecated) { if !flag.contains('@VMODROOT') { diff --git a/vlib/v/checker/comptime.v b/vlib/v/checker/comptime.v index 27a6b7a3ef96ed..c7ba8175725d0a 100644 --- a/vlib/v/checker/comptime.v +++ b/vlib/v/checker/comptime.v @@ -29,6 +29,26 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type { node.env_value = env_value 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 { + c.error(err.msg(), node.pos) + return ast.void_type + } + node.compile_value = value + node.result_type = typ + return typ + } if node.is_embed { if node.args.len == 1 { embed_arg := node.args[0] @@ -569,6 +589,42 @@ 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') { @@ -969,6 +1025,16 @@ fn (mut c Checker) comptime_if_branch(mut cond ast.Expr, pos token.Pos) Comptime return .skip } m.run() or { return .skip } + return .eval + } + if cond.is_compile_value { + t := c.expr(mut cond) + if t != ast.bool_type { + c.error('inside \$if, only \$d() expressions that return bool are allowed', + cond.pos) + return .skip + } + return .unknown // always fully generate the code for that branch } return .eval } diff --git a/vlib/v/checker/tests/comptime_value_d_in_include_errors.out b/vlib/v/checker/tests/comptime_value_d_in_include_errors.out new file mode 100644 index 00000000000000..58b4a71ccb2998 --- /dev/null +++ b/vlib/v/checker/tests/comptime_value_d_in_include_errors.out @@ -0,0 +1 @@ +builder error: Header file "/opt/invalid/include/stdio.h", needed for module `main` was not found. Please install the corresponding development headers. diff --git a/vlib/v/checker/tests/comptime_value_d_in_include_errors.vv b/vlib/v/checker/tests/comptime_value_d_in_include_errors.vv new file mode 100644 index 00000000000000..7557f5d9915573 --- /dev/null +++ b/vlib/v/checker/tests/comptime_value_d_in_include_errors.vv @@ -0,0 +1 @@ +#include "$d('my_include','/opt/invalid/include')/stdio.h" 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 new file mode 100644 index 00000000000000..17bb7e081bd1fb --- /dev/null +++ b/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.out @@ -0,0 +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 + 1 | const my_f32 = $d('my_f32', f32(42.0)) + | ~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.vv b/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.vv new file mode 100644 index 00000000000000..7e52c4f3b4dcf7 --- /dev/null +++ b/vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.vv @@ -0,0 +1 @@ +const my_f32 = $d('my_f32', f32(42.0)) 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 new file mode 100644 index 00000000000000..d1e104b41f04b1 --- /dev/null +++ b/vlib/v/checker/tests/run/using_comptime_d_value.run.out @@ -0,0 +1,3 @@ +42.0 +false +done diff --git a/vlib/v/checker/tests/run/using_comptime_d_value.vv b/vlib/v/checker/tests/run/using_comptime_d_value.vv new file mode 100644 index 00000000000000..75725a79420bed --- /dev/null +++ b/vlib/v/checker/tests/run/using_comptime_d_value.vv @@ -0,0 +1,11 @@ +#flag -I $d('my_flag','flag_value')/xyz +#include "@VMODROOT/$d('my_include','vlib/v')/tests/project_with_c_code/mod1/c/header.h" + +const my_f64 = $d('my_f64', 42.0) + +fn main() { + println(my_f64) + cv_bool := $d('my_bool', false) + println(cv_bool) + println('done') +} diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index c5b8e1a5fe59d0..f4dd7ad9ef4676 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -237,6 +237,7 @@ fn (mut tasks Tasks) run() { // cleaner error message, than a generic C error, but without the explanation. m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv' m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' + m_skip_files << 'vlib/v/checker/tests/comptime_value_d_in_include_errors.vv' } $if msvc { m_skip_files << 'vlib/v/checker/tests/asm_alias_does_not_exist.vv' @@ -244,6 +245,7 @@ fn (mut tasks Tasks) run() { // TODO: investigate why MSVC regressed m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv' m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv' + m_skip_files << 'vlib/v/checker/tests/comptime_value_d_in_include_errors.vv' } $if windows { m_skip_files << 'vlib/v/checker/tests/modules/deprecated_module' diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index e553382e4eab1f..c9f85ce8fff4e7 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -2186,6 +2186,11 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) { f.write("\$${node.method_name}('${node.args_var}')") } } + node.method_name == 'd' { + f.write("\$d('${node.args_var}', ") + f.expr(node.args[0].expr) + f.write(')') + } node.method_name == 'res' { if node.args_var != '' { f.write('\$res(${node.args_var})') diff --git a/vlib/v/fmt/tests/comptime_value_keep.vv b/vlib/v/fmt/tests/comptime_value_keep.vv new file mode 100644 index 00000000000000..7bb474537bd77e --- /dev/null +++ b/vlib/v/fmt/tests/comptime_value_keep.vv @@ -0,0 +1,7 @@ +fn main() { + val_str := $d('key_str', 'value') + val_f64 := $d('key_f64', 42.0) + val_int := $d('key_int', 56) + val_bool := $d('key_bool', false) + val_char := $d('key_char', `f`) +} diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index c1d198d1d52e64..ad2786c8a1ded3 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -48,10 +48,23 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) { } if node.method_name == 'env' { // $env('ENV_VAR_NAME') + // TODO: deprecate after support for $d() is stable val := util.cescaped_path(os.getenv(node.args_var)) g.write('_SLIT("${val}")') return } + if node.method_name == 'd' { + // $d('some_string',), affected by `-d some_string=actual_value` + val := util.cescaped_path(node.compile_value) + if node.result_type == ast.string_type { + g.write('_SLIT("${val}")') + } else if node.result_type == ast.char_type { + g.write("'${val}'") + } else { + g.write('${val}') + } + return + } if node.method_name == 'res' { if node.args_var != '' { g.write('${g.defer_return_tmp_var}.arg${node.args_var}') @@ -677,7 +690,22 @@ fn (mut g Gen) comptime_if_cond(cond ast.Expr, pkg_exist bool) (bool, bool) { return true, false } ast.ComptimeCall { - g.write('${pkg_exist}') + if cond.method_name == 'pkgconfig' { + g.write('${pkg_exist}') + return true, false + } + if cond.method_name == 'd' { + if cond.result_type == ast.bool_type { + if cond.compile_value == 'true' { + g.write('1') + } else { + g.write('0') + } + } else { + g.write('defined(CUSTOM_DEFINE_${cond.args_var})') + } + return true, false + } return true, false } ast.SelectorExpr { diff --git a/vlib/v/gen/c/testdata/use_flag_comptime_values.out b/vlib/v/gen/c/testdata/use_flag_comptime_values.out new file mode 100644 index 00000000000000..d343dc6355d107 --- /dev/null +++ b/vlib/v/gen/c/testdata/use_flag_comptime_values.out @@ -0,0 +1,5 @@ +2.0 +3 +a four +true +g diff --git a/vlib/v/gen/c/testdata/use_flag_comptime_values.vv b/vlib/v/gen/c/testdata/use_flag_comptime_values.vv new file mode 100644 index 00000000000000..7f17b36879e686 --- /dev/null +++ b/vlib/v/gen/c/testdata/use_flag_comptime_values.vv @@ -0,0 +1,20 @@ +// 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 +const my_f64 = $d('my_f64', 1.0) +const my_i64 = $d('my_i64', 2) +const my_string = $d('my_string', 'three') +const my_bool = $d('my_bool', false) +const my_char = $d('my_char', `f`) + +fn main() { + assert my_f64 == 2.0 + assert my_i64 == 3 + assert my_string == 'a four' + assert my_bool == true + assert my_char == `g` + println(my_f64) + println(my_i64) + println(my_string) + println(my_bool) + println(rune(my_char)) +} diff --git a/vlib/v/gen/c/testdata/use_flag_comptime_with_if.out b/vlib/v/gen/c/testdata/use_flag_comptime_with_if.out new file mode 100644 index 00000000000000..a12528a00a8f4a --- /dev/null +++ b/vlib/v/gen/c/testdata/use_flag_comptime_with_if.out @@ -0,0 +1,44 @@ +>>>>> Should be compiled with: `./v -d ddd1=true -d ddd2=false` +>>>>> Note that ddd3 is not passed *on purpose*, to test the default . +=========================================================================== +$d(ddd1, false) : true +$d(ddd1, true) : true +------------------------------------------------------------ +Checking $if with default false: +1 ddd1 is true +Checking $if with default true: +2 ddd1 is true +------------------------------------------------------------ +Checking $if !$d(ddd1) with default false: +3 !ddd1 is false +Checking $if !$d() with default true: +4 !ddd1 is false + +=========================================================================== +$d(ddd2, false) : false +$d(ddd2, true) : false +------------------------------------------------------------ +Checking $if with default false: +1 ddd2 is false +Checking $if with default true: +2 ddd2 is false +------------------------------------------------------------ +Checking $if !$d() with default false: +3 !ddd2 is true +Checking $if !$d() with default true: +4 !ddd2 is true + +=========================================================================== +$d(ddd3, false) : false +$d(ddd3, true) : true +------------------------------------------------------------ +Checking $if with default false: +1 ddd3 is false +Checking $if with default true: +2 ddd3 is true +------------------------------------------------------------ +Checking $if !$d() with default false: +3 !ddd3 is true +Checking $if !$d() with default true: +4 !ddd3 is false + diff --git a/vlib/v/gen/c/testdata/use_flag_comptime_with_if.vv b/vlib/v/gen/c/testdata/use_flag_comptime_with_if.vv new file mode 100644 index 00000000000000..89bb7916d8e151 --- /dev/null +++ b/vlib/v/gen/c/testdata/use_flag_comptime_with_if.vv @@ -0,0 +1,111 @@ +// vtest vflags: -d ddd1=true -d ddd2=false + +fn main() { + println('>>>>> Should be compiled with: `./v -d ddd1=true -d ddd2=false`') + println('>>>>> Note that ddd3 is not passed *on purpose*, to test the default .') + assert $d('ddd1', false) == true + assert $d('ddd2', true) == false + assert $d('ddd3', true) == true + check_ddd1() + check_ddd2() + check_ddd3() +} + +fn check_ddd1() { + println('===========================================================================') + println('\$d(ddd1, false) : ' + $d('ddd1', false).str()) + println('\$d(ddd1, true) : ' + $d('ddd1', true).str()) + println('------------------------------------------------------------') + println('Checking \$if with default false:') + $if $d('ddd1', false) { + println('1 ddd1 is true') + } $else { + println('1 ddd1 is false') + } + println('Checking \$if with default true:') + $if $d('ddd1', true) { + println('2 ddd1 is true') + } $else { + println('2 ddd1 is false') + } + println('------------------------------------------------------------') + println('Checking \$if !\$d(ddd1) with default false:') + $if !$d('ddd1', false) { + println('3 !ddd1 is true') + } $else { + println('3 !ddd1 is false') + } + println('Checking \$if !\$d() with default true:') + $if !$d('ddd1', true) { + println('4 !ddd1 is true') + } $else { + println('4 !ddd1 is false') + } + println('') +} + +fn check_ddd2() { + println('===========================================================================') + println('\$d(ddd2, false) : ' + $d('ddd2', false).str()) + println('\$d(ddd2, true) : ' + $d('ddd2', true).str()) + println('------------------------------------------------------------') + println('Checking \$if with default false:') + $if $d('ddd2', false) { + println('1 ddd2 is true') + } $else { + println('1 ddd2 is false') + } + println('Checking \$if with default true:') + $if $d('ddd2', true) { + println('2 ddd2 is true') + } $else { + println('2 ddd2 is false') + } + println('------------------------------------------------------------') + println('Checking \$if !\$d() with default false:') + $if !$d('ddd2', false) { + println('3 !ddd2 is true') + } $else { + println('3 !ddd2 is false') + } + println('Checking \$if !\$d() with default true:') + $if !$d('ddd2', true) { + println('4 !ddd2 is true') + } $else { + println('4 !ddd2 is false') + } + println('') +} + +fn check_ddd3() { + println('===========================================================================') + println('\$d(ddd3, false) : ' + $d('ddd3', false).str()) + println('\$d(ddd3, true) : ' + $d('ddd3', true).str()) + println('------------------------------------------------------------') + println('Checking \$if with default false:') + $if $d('ddd3', false) { + println('1 ddd3 is true') + } $else { + println('1 ddd3 is false') + } + println('Checking \$if with default true:') + $if $d('ddd3', true) { + println('2 ddd3 is true') + } $else { + println('2 ddd3 is false') + } + println('------------------------------------------------------------') + println('Checking \$if !\$d() with default false:') + $if !$d('ddd3', false) { + println('3 !ddd3 is true') + } $else { + println('3 !ddd3 is false') + } + println('Checking \$if !\$d() with default true:') + $if !$d('ddd3', true) { + println('4 !ddd3 is true') + } $else { + println('4 !ddd3 is false') + } + println('') +} diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 42c5fb21218746..367a555e17bf0a 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -8,7 +8,7 @@ import v.ast import v.token const supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig', 'compile_error', - 'compile_warn', 'res'] + 'compile_warn', 'd', 'res'] const comptime_types = ['map', 'array', 'array_dynamic', 'array_fixed', 'int', 'float', 'struct', 'interface', 'enum', 'sumtype', 'alias', 'function', 'option', 'string'] @@ -106,7 +106,7 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall { } start_pos := p.tok.pos() p.check(.dollar) - error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$vweb.html()`, `\$compile_error()`, `\$compile_warn()` and `\$res()` comptime functions are supported right now' + error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$vweb.html()`, `\$compile_error()`, `\$compile_warn()`, `\$d()` and `\$res()` comptime functions are supported right now' if p.peek_tok.kind == .dot { name := p.check_name() // skip `vweb.html()` TODO if name != 'vweb' && name != 'veb' { @@ -122,7 +122,6 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall { } is_embed_file := method_name == 'embed_file' is_html := method_name == 'html' - // $env('ENV_VAR_NAME') p.check(.lpar) arg_pos := p.tok.pos() if method_name in ['env', 'pkgconfig', 'compile_error', 'compile_warn'] { @@ -160,6 +159,27 @@ fn (mut p Parser) comptime_call() ast.ComptimeCall { method_name: method_name pos: start_pos.extend(p.prev_tok.pos()) } + } else if method_name == 'd' { + const_string := p.tok.lit + // const_name_pos := p.tok.pos() + p.check(.string) + p.check(.comma) + arg_expr := p.expr(0) + args := [ + ast.CallArg{ + expr: arg_expr + pos: p.tok.pos() + }, + ] + p.check(.rpar) + return ast.ComptimeCall{ + scope: unsafe { nil } + is_compile_value: true + method_name: method_name + args_var: const_string + args: args + pos: start_pos.extend(p.prev_tok.pos()) + } } has_string_arg := p.tok.kind == .string mut literal_string_param := if is_html && !has_string_arg { '' } else { p.tok.lit } diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index f37fc529cb5ae8..9b4e3b0f7afe79 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -193,6 +193,7 @@ pub mut: // -d vfmt and -d another=0 for `$if vfmt { will execute }` and `$if another ? { will NOT get here }` compile_defines []string // just ['vfmt'] compile_defines_all []string // contains both: ['vfmt','another'] + compile_values map[string]string // the map will contain for `-d key=value`: compile_values['key'] = 'value', and for `-d ident`, it will be: compile_values['ident'] = 'true' // run_args []string // `v run x.v 1 2 3` => `1 2 3` printfn_list []string // a list of generated function names, whose source should be shown, for debugging @@ -1170,39 +1171,37 @@ pub fn cc_from_string(s string) CompilerType { } } +fn (mut prefs Preferences) parse_compile_value(define string) { + if !define.contains('=') { + eprintln_exit('V error: Define argument value missing for ${define}.') + return + } + name := define.all_before('=') + value := define.all_after_first('=') + prefs.compile_values[name] = value +} + fn (mut prefs Preferences) parse_define(define string) { - define_parts := define.split('=') - prefs.diagnose_deprecated_defines(define_parts) if !(prefs.is_debug && define == 'debug') { prefs.build_options << '-d ${define}' } - if define_parts.len == 1 { + if !define.contains('=') { + prefs.compile_values[define] = 'true' prefs.compile_defines << define prefs.compile_defines_all << define return } - if define_parts.len == 2 { - prefs.compile_defines_all << define_parts[0] - match define_parts[1] { - '0' {} - '1' { - prefs.compile_defines << define_parts[0] - } - else { - eprintln_exit( - 'V error: Unknown define argument value `${define_parts[1]}` for ${define_parts[0]}.' + - ' Expected `0` or `1`.') - } + dname := define.all_before('=') + dvalue := define.all_after_first('=') + prefs.compile_values[dname] = dvalue + prefs.compile_defines_all << dname + match dvalue { + '0' {} + '1' { + prefs.compile_defines << dname } - return + else {} } - eprintln_exit('V error: Unknown define argument: ${define}. Expected at most one `=`.') -} - -fn (mut prefs Preferences) diagnose_deprecated_defines(define_parts []string) { - // if define_parts[0] == 'no_bounds_checking' { - // eprintln('`-d no_bounds_checking` was deprecated in 2022/10/30. Use `-no-bounds-checking` instead.') - // } } pub fn supported_test_runners_list() string { diff --git a/vlib/v/tests/comptime_value_d_default_test.v b/vlib/v/tests/comptime_value_d_default_test.v new file mode 100644 index 00000000000000..a9927d8969fa56 --- /dev/null +++ b/vlib/v/tests/comptime_value_d_default_test.v @@ -0,0 +1,13 @@ +const my_f64 = $d('my_f64', 1.0) +const my_i64 = $d('my_i64', 2) +const my_string = $d('my_string', 'three') +const my_bool = $d('my_bool', false) +const my_char = $d('my_char', `f`) + +fn test_default_compile_values() { + assert my_f64 == 1.0 + assert my_i64 == 2 + assert my_string == 'three' + assert my_bool == false + assert my_char == `f` +} diff --git a/vlib/v/util/util.v b/vlib/v/util/util.v index eb7f4be57b92f6..edd14c1c09758f 100644 --- a/vlib/v/util/util.v +++ b/vlib/v/util/util.v @@ -115,6 +115,71 @@ pub fn resolve_env_value(str string, check_for_presence bool) !string { return rep } +const d_sig = "\$d('" + +// resolve_d_value replaces all occurrences of `$d('ident','value')` +// in `str` with either the default `'value'` param or a compile value passed via `-d ident=value`. +pub fn resolve_d_value(compile_values map[string]string, str string) !string { + at := str.index(util.d_sig) or { + return error('no "${util.d_sig}' + '...\')" could be found in "${str}".') + } + mut all_parsed := util.d_sig + mut ch := u8(`.`) + mut d_ident := '' + mut i := 0 + for i = at + util.d_sig.len; i < str.len && ch != `'`; i++ { + ch = u8(str[i]) + all_parsed += ch.ascii_str() + if ch.is_letter() || ch.is_digit() || ch == `_` { + d_ident += ch.ascii_str() + } else { + if !(ch == `'`) { + if ch == `$` { + return error('cannot use string interpolation in compile time \$d() expression') + } + return error('invalid `\$d` identifier in "${str}", invalid character "${ch.ascii_str()}"') + } + } + } + if d_ident == '' { + return error('first argument of `\$d` must be a string identifier') + } + + // at this point we should have a valid identifier in `d_ident`. + // Next we parse out the default string value + + // advance past the `,` and the opening `'` in second argument, or ... eat whatever is there + all_parsed += u8(str[i]).ascii_str() + i++ + all_parsed += u8(str[i]).ascii_str() + i++ + // Rinse, repeat for the expected default value string + ch = u8(`.`) + mut d_default_value := '' + for ; i < str.len && ch != `'`; i++ { + ch = u8(str[i]) + all_parsed += ch.ascii_str() + if !(ch == `'`) { + d_default_value += ch.ascii_str() + } + if ch == `$` { + return error('cannot use string interpolation in compile time \$d() expression') + } + } + if d_default_value == '' { + return error('second argument of `\$d` must be a pure literal') + } + // at this point we have the identifier and the default value. + // now we need to resolve which one to use from `compile_values`. + d_value := compile_values[d_ident] or { d_default_value } + // if more `$d()` calls remains, resolve those as well: + rep := str.replace_once(all_parsed + ')', d_value) + if rep.contains(util.d_sig) { + return resolve_d_value(compile_values, rep) + } + return rep +} + // 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`.