comptime: support -d ident=value and var := $d('ident', 0) (#21685)

This commit is contained in:
larpon 2024-06-19 06:24:16 +02:00 committed by GitHub
parent aaa23bb058
commit 78b77b9f14
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 533 additions and 46 deletions

View File

@ -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', <default value>)`.
## V 0.4.6
*20 May 2024*

View File

@ -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))

View File

@ -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

View File

@ -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
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
args_var string
args []CallArg
embed_file EmbeddedFile
or_block OrExpr
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 {

View File

@ -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') {

View File

@ -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
}

View File

@ -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.

View File

@ -0,0 +1 @@
#include "$d('my_include','/opt/invalid/include')/stdio.h"

View File

@ -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))
| ~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1 @@
const my_f32 = $d('my_f32', f32(42.0))

View File

@ -0,0 +1,3 @@
42.0
false
done

View File

@ -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')
}

View File

@ -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'

View File

@ -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})')

View File

@ -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`)
}

View File

@ -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',<default value>), 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 {

View File

@ -0,0 +1,5 @@
2.0
3
a four
true
g

View File

@ -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))
}

View File

@ -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

View File

@ -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('')
}

View File

@ -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 }

View File

@ -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 {

View File

@ -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`
}

View File

@ -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`.