Skip to content

Commit 78b77b9

Browse files
authored
comptime: support -d ident=value and var := $d('ident', 0) (#21685)
1 parent aaa23bb commit 78b77b9

24 files changed

+534
-47
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## V 0.4.7 (NEXT)
2+
3+
#### Improvements in the language
4+
- comptime: add support for `-d ident=value` and retrieval in code via `$d('ident', <default value>)`.
5+
16
## V 0.4.6
27
*20 May 2024*
38

cmd/tools/vast/vast.v

+1
Original file line numberDiff line numberDiff line change
@@ -1019,6 +1019,7 @@ fn (t Tree) comptime_call(node ast.ComptimeCall) &Node {
10191019
obj.add_terse('result_type', t.type_node(node.result_type))
10201020
obj.add('scope', t.scope(node.scope))
10211021
obj.add_terse('env_value', t.string_node(node.env_value))
1022+
obj.add_terse('compile_value', t.string_node(node.compile_value))
10221023
obj.add('pos', t.pos(node.pos))
10231024
obj.add_terse('args', t.array_node_call_arg(node.args))
10241025
obj.add_terse('or_block', t.or_expr(node.or_block))

doc/docs.md

+61
Original file line numberDiff line numberDiff line change
@@ -6120,6 +6120,67 @@ V can bring in values at compile time from environment variables.
61206120
`$env('ENV_VAR')` can also be used in top-level `#flag` and `#include` statements:
61216121
`#flag linux -I $env('JAVA_HOME')/include`.
61226122
6123+
#### `$d`
6124+
6125+
V can bring in values at compile time from `-d ident=value` flag defines, passed on
6126+
the command line to the compiler. You can also pass `-d ident`, which will have the
6127+
same meaning as passing `-d ident=true`.
6128+
6129+
To get the value in your code, use: `$d('ident', default)`, where `default`
6130+
can be `false` for booleans, `0` or `123` for i64 numbers, `0.0` or `113.0`
6131+
for f64 numbers, `'a string'` for strings.
6132+
6133+
When a flag is not provided via the command line, `$d()` will return the `default`
6134+
value provided as the *second* argument.
6135+
6136+
```v
6137+
module main
6138+
6139+
const my_i64 = $d('my_i64', 1024)
6140+
6141+
fn main() {
6142+
compile_time_value := $d('my_string', 'V')
6143+
println(compile_time_value)
6144+
println(my_i64)
6145+
}
6146+
```
6147+
6148+
Running the above with `v run .` will output:
6149+
```
6150+
V
6151+
1024
6152+
```
6153+
6154+
Running the above with `v -d my_i64=4096 -d my_string="V rocks" run .` will output:
6155+
```
6156+
V rocks
6157+
4096
6158+
```
6159+
6160+
Here is an example of how to use the default values, which have to be *pure* literals:
6161+
```v
6162+
fn main() {
6163+
val_str := $d('id_str', 'value') // can be changed by providing `-d id_str="my id"`
6164+
val_f64 := $d('id_f64', 42.0) // can be changed by providing `-d id_f64=84.0`
6165+
val_i64 := $d('id_i64', 56) // can be changed by providing `-d id_i64=123`
6166+
val_bool := $d('id_bool', false) // can be changed by providing `-d id_bool=true`
6167+
val_char := $d('id_char', `f`) // can be changed by providing `-d id_char=v`
6168+
println(val_str)
6169+
println(val_f64)
6170+
println(val_i64)
6171+
println(val_bool)
6172+
println(rune(val_char))
6173+
}
6174+
```
6175+
6176+
`$d('ident','value')` can also be used in top-level statements like `#flag` and `#include`:
6177+
`#flag linux -I $d('my_include','/usr')/include`. The default value for `$d` when used in these
6178+
statements should be literal `string`s.
6179+
6180+
`$d('ident', false)` can also be used inside `$if $d('ident', false) {` statements,
6181+
granting you the ability to selectively turn on/off certain sections of code, at compile
6182+
time, without modifying your source code, or keeping different versions of it.
6183+
61236184
#### `$compile_error` and `$compile_warn`
61246185
61256186
These two comptime functions are very useful for displaying custom errors/warnings during

vlib/v/ast/ast.v

+22-20
Original file line numberDiff line numberDiff line change
@@ -1913,26 +1913,28 @@ pub mut:
19131913
@[minify]
19141914
pub struct ComptimeCall {
19151915
pub:
1916-
pos token.Pos
1917-
has_parens bool // if $() is used, for vfmt
1918-
method_name string
1919-
method_pos token.Pos
1920-
scope &Scope = unsafe { nil }
1921-
is_vweb bool
1922-
is_embed bool
1923-
is_env bool
1924-
env_pos token.Pos
1925-
is_pkgconfig bool
1926-
pub mut:
1927-
vweb_tmpl File
1928-
left Expr
1929-
left_type Type
1930-
result_type Type
1931-
env_value string
1932-
args_var string
1933-
args []CallArg
1934-
embed_file EmbeddedFile
1935-
or_block OrExpr
1916+
pos token.Pos
1917+
has_parens bool // if $() is used, for vfmt
1918+
method_name string
1919+
method_pos token.Pos
1920+
scope &Scope = unsafe { nil }
1921+
is_vweb bool
1922+
is_embed bool // $embed_file(...)
1923+
is_env bool // $env(...) // TODO: deprecate after $d() is stable
1924+
is_compile_value bool // $d(...)
1925+
env_pos token.Pos
1926+
is_pkgconfig bool
1927+
pub mut:
1928+
vweb_tmpl File
1929+
left Expr
1930+
left_type Type
1931+
result_type Type
1932+
env_value string
1933+
compile_value string
1934+
args_var string
1935+
args []CallArg
1936+
embed_file EmbeddedFile
1937+
or_block OrExpr
19361938
}
19371939

19381940
pub struct None {

vlib/v/checker/checker.v

+13
Original file line numberDiff line numberDiff line change
@@ -2428,6 +2428,13 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
24282428
}
24292429
node.main = env
24302430
}
2431+
if flag.contains('\$d(') {
2432+
d := util.resolve_d_value(c.pref.compile_values, flag) or {
2433+
c.error(err.msg(), node.pos)
2434+
return
2435+
}
2436+
node.main = d
2437+
}
24312438
flag_no_comment := flag.all_before('//').trim_space()
24322439
if node.kind == 'include' || node.kind == 'preinclude' {
24332440
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) {
25112518
return
25122519
}
25132520
}
2521+
if flag.contains('\$d(') {
2522+
flag = util.resolve_d_value(c.pref.compile_values, flag) or {
2523+
c.error(err.msg(), node.pos)
2524+
return
2525+
}
2526+
}
25142527
for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] {
25152528
if flag.contains(deprecated) {
25162529
if !flag.contains('@VMODROOT') {

vlib/v/checker/comptime.v

+66
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) ast.Type {
2929
node.env_value = env_value
3030
return ast.string_type
3131
}
32+
if node.is_compile_value {
33+
arg := node.args[0] or {
34+
c.error('\$d() takes two arguments, a string and a primitive literal', node.pos)
35+
return ast.void_type
36+
}
37+
if !arg.expr.is_pure_literal() {
38+
c.error('-d values can only be pure literals', node.pos)
39+
return ast.void_type
40+
}
41+
typ := arg.expr.get_pure_type()
42+
arg_as_string := arg.str().trim('`"\'')
43+
value := c.pref.compile_values[node.args_var] or { arg_as_string }
44+
validate_type_string_is_pure_literal(typ, value) or {
45+
c.error(err.msg(), node.pos)
46+
return ast.void_type
47+
}
48+
node.compile_value = value
49+
node.result_type = typ
50+
return typ
51+
}
3252
if node.is_embed {
3353
if node.args.len == 1 {
3454
embed_arg := node.args[0]
@@ -569,6 +589,42 @@ fn (mut c Checker) eval_comptime_const_expr(expr ast.Expr, nlevel int) ?ast.Comp
569589
return none
570590
}
571591

592+
fn validate_type_string_is_pure_literal(typ ast.Type, str string) ! {
593+
if typ == ast.bool_type {
594+
if !(str == 'true' || str == 'false') {
595+
return error('bool literal `true` or `false` expected, found "${str}"')
596+
}
597+
} else if typ == ast.char_type {
598+
if str.starts_with('\\') {
599+
if str.len <= 1 {
600+
return error('empty escape sequence found')
601+
}
602+
if !is_escape_sequence(str[1]) {
603+
return error('char literal escape sequence expected, found "${str}"')
604+
}
605+
} else if str.len != 1 {
606+
return error('char literal expected, found "${str}"')
607+
}
608+
} else if typ == ast.f64_type {
609+
if str.count('.') != 1 {
610+
return error('f64 literal expected, found "${str}"')
611+
}
612+
} else if typ == ast.string_type {
613+
} else if typ == ast.i64_type {
614+
if !str.is_int() {
615+
return error('i64 literal expected, found "${str}"')
616+
}
617+
} else {
618+
return error('expected pure literal, found "${str}"')
619+
}
620+
}
621+
622+
@[inline]
623+
fn is_escape_sequence(c u8) bool {
624+
return c in [`x`, `u`, `e`, `n`, `r`, `t`, `v`, `a`, `f`, `b`, `\\`, `\``, `$`, `@`, `?`, `{`,
625+
`}`, `'`, `"`, `U`]
626+
}
627+
572628
fn (mut c Checker) verify_vweb_params_for_method(node ast.Fn) (bool, int, int) {
573629
margs := node.params.len - 1 // first arg is the receiver/this
574630
// 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
9691025
return .skip
9701026
}
9711027
m.run() or { return .skip }
1028+
return .eval
1029+
}
1030+
if cond.is_compile_value {
1031+
t := c.expr(mut cond)
1032+
if t != ast.bool_type {
1033+
c.error('inside \$if, only \$d() expressions that return bool are allowed',
1034+
cond.pos)
1035+
return .skip
1036+
}
1037+
return .unknown // always fully generate the code for that branch
9721038
}
9731039
return .eval
9741040
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
builder error: Header file "/opt/invalid/include/stdio.h", needed for module `main` was not found. Please install the corresponding development headers.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "$d('my_include','/opt/invalid/include')/stdio.h"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vlib/v/checker/tests/comptime_value_d_values_can_only_be_pure_literals.vv:1:16: error: -d values can only be pure literals
2+
1 | const my_f32 = $d('my_f32', f32(42.0))
3+
| ~~~~~~~~~~~~~~~~~~~~~~~
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const my_f32 = $d('my_f32', f32(42.0))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
42.0
2+
false
3+
done
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#flag -I $d('my_flag','flag_value')/xyz
2+
#include "@VMODROOT/$d('my_include','vlib/v')/tests/project_with_c_code/mod1/c/header.h"
3+
4+
const my_f64 = $d('my_f64', 42.0)
5+
6+
fn main() {
7+
println(my_f64)
8+
cv_bool := $d('my_bool', false)
9+
println(cv_bool)
10+
println('done')
11+
}

vlib/v/compiler_errors_test.v

+2
Original file line numberDiff line numberDiff line change
@@ -237,13 +237,15 @@ fn (mut tasks Tasks) run() {
237237
// cleaner error message, than a generic C error, but without the explanation.
238238
m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv'
239239
m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv'
240+
m_skip_files << 'vlib/v/checker/tests/comptime_value_d_in_include_errors.vv'
240241
}
241242
$if msvc {
242243
m_skip_files << 'vlib/v/checker/tests/asm_alias_does_not_exist.vv'
243244
m_skip_files << 'vlib/v/checker/tests/asm_immutable_err.vv'
244245
// TODO: investigate why MSVC regressed
245246
m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_1.vv'
246247
m_skip_files << 'vlib/v/checker/tests/missing_c_lib_header_with_explanation_2.vv'
248+
m_skip_files << 'vlib/v/checker/tests/comptime_value_d_in_include_errors.vv'
247249
}
248250
$if windows {
249251
m_skip_files << 'vlib/v/checker/tests/modules/deprecated_module'

vlib/v/fmt/fmt.v

+5
Original file line numberDiff line numberDiff line change
@@ -2186,6 +2186,11 @@ pub fn (mut f Fmt) comptime_call(node ast.ComptimeCall) {
21862186
f.write("\$${node.method_name}('${node.args_var}')")
21872187
}
21882188
}
2189+
node.method_name == 'd' {
2190+
f.write("\$d('${node.args_var}', ")
2191+
f.expr(node.args[0].expr)
2192+
f.write(')')
2193+
}
21892194
node.method_name == 'res' {
21902195
if node.args_var != '' {
21912196
f.write('\$res(${node.args_var})')
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
val_str := $d('key_str', 'value')
3+
val_f64 := $d('key_f64', 42.0)
4+
val_int := $d('key_int', 56)
5+
val_bool := $d('key_bool', false)
6+
val_char := $d('key_char', `f`)
7+
}

vlib/v/gen/c/comptime.v

+29-1
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,23 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) {
4848
}
4949
if node.method_name == 'env' {
5050
// $env('ENV_VAR_NAME')
51+
// TODO: deprecate after support for $d() is stable
5152
val := util.cescaped_path(os.getenv(node.args_var))
5253
g.write('_SLIT("${val}")')
5354
return
5455
}
56+
if node.method_name == 'd' {
57+
// $d('some_string',<default value>), affected by `-d some_string=actual_value`
58+
val := util.cescaped_path(node.compile_value)
59+
if node.result_type == ast.string_type {
60+
g.write('_SLIT("${val}")')
61+
} else if node.result_type == ast.char_type {
62+
g.write("'${val}'")
63+
} else {
64+
g.write('${val}')
65+
}
66+
return
67+
}
5568
if node.method_name == 'res' {
5669
if node.args_var != '' {
5770
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) {
677690
return true, false
678691
}
679692
ast.ComptimeCall {
680-
g.write('${pkg_exist}')
693+
if cond.method_name == 'pkgconfig' {
694+
g.write('${pkg_exist}')
695+
return true, false
696+
}
697+
if cond.method_name == 'd' {
698+
if cond.result_type == ast.bool_type {
699+
if cond.compile_value == 'true' {
700+
g.write('1')
701+
} else {
702+
g.write('0')
703+
}
704+
} else {
705+
g.write('defined(CUSTOM_DEFINE_${cond.args_var})')
706+
}
707+
return true, false
708+
}
681709
return true, false
682710
}
683711
ast.SelectorExpr {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2.0
2+
3
3+
a four
4+
true
5+
g
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This file should pass if compiled/run with:
2+
// vtest vflags: -d my_f64=2.0 -d my_i64=3 -d my_string="a four" -d my_bool -d my_char=g
3+
const my_f64 = $d('my_f64', 1.0)
4+
const my_i64 = $d('my_i64', 2)
5+
const my_string = $d('my_string', 'three')
6+
const my_bool = $d('my_bool', false)
7+
const my_char = $d('my_char', `f`)
8+
9+
fn main() {
10+
assert my_f64 == 2.0
11+
assert my_i64 == 3
12+
assert my_string == 'a four'
13+
assert my_bool == true
14+
assert my_char == `g`
15+
println(my_f64)
16+
println(my_i64)
17+
println(my_string)
18+
println(my_bool)
19+
println(rune(my_char))
20+
}

0 commit comments

Comments
 (0)