Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 26 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
3998710
comptime: support `-cv key=value` and `var := $compile_value/2`
larpon Jun 15, 2024
a3ee35a
comptime: add `vfmt` support, tests, CI job for `$compile_value`
larpon Jun 16, 2024
f5bf59c
ci: update .github/workflows/comptime_ci.yml
larpon Jun 16, 2024
df29e38
ci: update .github/workflows/comptime_ci.yml
larpon Jun 16, 2024
6913bb3
remove comptime_ci.yml, move use_flag_comptime_values.vv to vlib/v/ge…
spytheman Jun 16, 2024
ab2f197
compile_compile_values -> compile_values, small cleanup of the CLI pa…
spytheman Jun 16, 2024
95a7e90
small cleanup of fmt.v
spytheman Jun 16, 2024
c4a8028
pref: save the -cv options to build_options too, so that they can be …
spytheman Jun 16, 2024
ce7a17f
support `-d ident=value` in addition to `-d ident`
spytheman Jun 17, 2024
8d9a23e
use `$d()`, instead of `$compile_value()`
spytheman Jun 17, 2024
e1be3eb
comptime: add simple string support for `$d()` in `#flag`,`#include` …
larpon Jun 18, 2024
a70006d
docs: document `$d` in `docs.md` and `CHANGELOG.md`
larpon Jun 18, 2024
4e68c08
tests: use `my_i64` over `my_int` to clearify the default value of `$…
larpon Jun 18, 2024
3714382
tests: use `my_i64` over `my_int` to clearify the default value of `$…
larpon Jun 18, 2024
2c0d463
docs: fix via 2x`VAUTOFIX=1 ./v check-md doc/docs.md`
larpon Jun 18, 2024
e629108
Merge branch 'master' into comptime/key-value
larpon Jun 18, 2024
9e61095
docs: add output examples with and without `-d` usage
larpon Jun 18, 2024
ed003d7
docs: remove surplus "`" character
larpon Jun 18, 2024
187eb64
checker,cgen: fix `$if $d(hi, false) {` bug
spytheman Jun 18, 2024
a485534
docs: use txt for the operator appendix
spytheman Jun 18, 2024
f19079d
docs: describe `$if $d(ident, false) {`, expand the existing descript…
spytheman Jun 18, 2024
82e8d22
docs: restore `v ignore` for the operator appendix
spytheman Jun 18, 2024
27e6fa9
v.util: small cleanup, extract d_sig as a constant
spytheman Jun 18, 2024
a90f39d
tests: move files to places where they will be used by `./v vlib/v/co…
spytheman Jun 18, 2024
1315786
fix for using_comptime_d_value.vv (include relative to the main V pro…
spytheman Jun 18, 2024
be03448
Update vlib/v/util/util.v
spytheman Jun 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/tools/vast/vast.v
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
42 changes: 22 additions & 20 deletions vlib/v/ast/ast.v
Original file line number Diff line number Diff line change
Expand Up @@ -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(...)
is_compile_value bool // $compile_value(...)
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 {
Expand Down
8 changes: 8 additions & 0 deletions vlib/v/checker/checker.v
Original file line number Diff line number Diff line change
Expand Up @@ -2428,6 +2428,10 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
}
node.main = env
}
if flag.contains('\$compile_value(') {
c.error('\$compile_value is not supported in ${node.kind} yet', node.pos)
return
}
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('"'))
Expand Down Expand Up @@ -2511,6 +2515,10 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
return
}
}
if flag.contains('\$compile_value(') {
c.error('\$compile_value is not supported in ${node.kind} yet', node.pos)
return
}
for deprecated in ['@VMOD', '@VMODULE', '@VPATH', '@VLIB_PATH'] {
if flag.contains(deprecated) {
if !flag.contains('@VMODROOT') {
Expand Down
57 changes: 57 additions & 0 deletions vlib/v/checker/comptime.v
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,27 @@ 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('\$compile_value takes two arguments, a string and a primitive literal',
node.pos)
return ast.void_type
}
if !arg.expr.is_pure_literal() {
c.error('-cv/-compile-value 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]
Expand Down Expand Up @@ -569,6 +590,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') {
Expand Down
13 changes: 13 additions & 0 deletions vlib/v/checker/tests/comptime_value/default_comptime_values_test.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const my_f64 = $compile_value('my_f64', 1.0)
const my_int = $compile_value('my_int', 2)
const my_string = $compile_value('my_string', 'three')
const my_bool = $compile_value('my_bool', false)
const my_char = $compile_value('my_char', `f`)

fn test_default_compile_values() {
assert my_f64 == 1.0
assert my_int == 2
assert my_string == 'three'
assert my_bool == false
assert my_char == `f`
}
3 changes: 3 additions & 0 deletions vlib/v/checker/tests/comptime_value/parser_errors_1.run.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
vlib/v/checker/tests/comptime_value/parser_errors_1.vv:1:16: error: -cv/-compile-value values can only be pure literals
1 | const my_f32 = $compile_value('my_f32', f32(42.0))
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 change: 1 addition & 0 deletions vlib/v/checker/tests/comptime_value/parser_errors_1.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const my_f32 = $compile_value('my_f32', f32(42.0))
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
42.0
false
done
11 changes: 11 additions & 0 deletions vlib/v/checker/tests/comptime_value/using_comptime_value.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// TODO: support #flag -I $const('my_flag','flag_value')/xyz
// TODO: support #include "$const('my_include','/usr/include')/stdio.h"

const my_f64 = $compile_value('my_f64', 42.0)

fn main() {
println(my_f64)
cv_bool := $compile_value('my_bool', false)
println(cv_bool)
println('done')
}
5 changes: 5 additions & 0 deletions vlib/v/fmt/fmt.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 == 'compile_value' {
f.write("\$compile_value('${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})')
Expand Down
7 changes: 7 additions & 0 deletions vlib/v/fmt/tests/comptime_value_keep.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
val_str := $compile_value('key_str', 'value')
val_f64 := $compile_value('key_f64', 42.0)
val_int := $compile_value('key_int', 56)
val_bool := $compile_value('key_bool', false)
val_char := $compile_value('key_char', `f`)
}
12 changes: 12 additions & 0 deletions vlib/v/gen/c/comptime.v
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ fn (mut g Gen) comptime_call(mut node ast.ComptimeCall) {
g.write('_SLIT("${val}")')
return
}
if node.method_name == 'compile_value' {
// $compile_value('some_string',<default 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}')
Expand Down
5 changes: 5 additions & 0 deletions vlib/v/gen/c/testdata/use_flag_comptime_values.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
2.0
3
a four
true
g
20 changes: 20 additions & 0 deletions vlib/v/gen/c/testdata/use_flag_comptime_values.vv
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// This file should pass if compiled/run with:
// vtest vflags: -cv my_f64=2.0 -cv my_int=3 -cv my_string="a four" -cv my_bool=true -cv my_char=g
const my_f64 = $compile_value('my_f64', 1.0)
const my_int = $compile_value('my_int', 2)
const my_string = $compile_value('my_string', 'three')
const my_bool = $compile_value('my_bool', false)
const my_char = $compile_value('my_char', `f`)

fn main() {
assert my_f64 == 2.0
assert my_int == 3
assert my_string == 'a four'
assert my_bool == true
assert my_char == `g`
println(my_f64)
println(my_int)
println(my_string)
println(my_bool)
println(rune(my_char))
}
26 changes: 23 additions & 3 deletions vlib/v/parser/comptime.v
Original file line number Diff line number Diff line change
Expand Up @@ -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', 'compile_value', 'res']
const comptime_types = ['map', 'array', 'array_dynamic', 'array_fixed', 'int', 'float', 'struct',
'interface', 'enum', 'sumtype', 'alias', 'function', 'option', 'string']

Expand Down Expand Up @@ -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()`, `\$compile_value()` 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' {
Expand All @@ -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'] {
Expand Down Expand Up @@ -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 == 'compile_value' {
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 }
Expand Down
17 changes: 17 additions & 0 deletions vlib/v/pref/pref.v
Original file line number Diff line number Diff line change
Expand Up @@ -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 // -compile_value key=value
//
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
Expand Down Expand Up @@ -823,6 +824,12 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin
res.build_options << '${arg} "${res.ldflags.trim_space()}"'
i++
}
'-cv', '-compile-value' {
if compile_value := args[i..][1] {
res.parse_compile_value(compile_value)
}
i++
}
'-d', '-define' {
if define := args[i..][1] {
res.parse_define(define)
Expand Down Expand Up @@ -1170,6 +1177,16 @@ 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)
Expand Down
Loading