From 5f4b34ef1256e768bc78bd37462f278d2202726f Mon Sep 17 00:00:00 2001 From: Felipe Pena Date: Tue, 28 Feb 2023 19:42:19 -0300 Subject: [PATCH] eval: add host API, for passing and receiving values, to/from code, ran by the eval.Eval instances (#17426) --- vlib/v/builder/builder.v | 12 +++++ vlib/v/builder/interpreterbuilder/ibuilder.v | 1 - vlib/v/eval/eval.v | 55 +++++++++++++++++--- vlib/v/eval/expr.v | 3 ++ vlib/v/eval/tests/push_val_test.v | 41 +++++++++++++++ vlib/v/pref/pref.v | 12 +++-- 6 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 vlib/v/eval/tests/push_val_test.v diff --git a/vlib/v/builder/builder.v b/vlib/v/builder/builder.v index d869f8fbe9..d20b6bee47 100644 --- a/vlib/v/builder/builder.v +++ b/vlib/v/builder/builder.v @@ -88,6 +88,18 @@ pub fn new_builder(pref_ &pref.Preferences) Builder { } } +pub fn (mut b Builder) interpret_text(code string, v_files []string) ! { + b.parsed_files = parser.parse_files(v_files, b.table, b.pref) + b.parsed_files << parser.parse_text(code, '', b.table, .skip_comments, b.pref) + b.parse_imports() + + if b.pref.only_check_syntax { + return error_with_code('stop_after_parser', 7001) + } + + b.middle_stages()! +} + pub fn (mut b Builder) front_stages(v_files []string) ! { mut timers := util.get_timers() util.timing_start('PARSE') diff --git a/vlib/v/builder/interpreterbuilder/ibuilder.v b/vlib/v/builder/interpreterbuilder/ibuilder.v index 452349d6cc..bfe88583f7 100644 --- a/vlib/v/builder/interpreterbuilder/ibuilder.v +++ b/vlib/v/builder/interpreterbuilder/ibuilder.v @@ -16,7 +16,6 @@ pub fn interpret_v(mut b builder.Builder) { files << b.get_user_files() b.set_module_lookup_paths() b.front_and_middle_stages(files) or { return } - util.timing_start('INTERPRET') mut e := eval.new_eval(b.table, b.pref) e.eval(mut b.parsed_files) diff --git a/vlib/v/eval/eval.v b/vlib/v/eval/eval.v index 46b68735f5..ca7c4a43ef 100644 --- a/vlib/v/eval/eval.v +++ b/vlib/v/eval/eval.v @@ -6,6 +6,7 @@ module eval import v.ast import v.pref import v.util +import v.builder pub fn new_eval(table &ast.Table, pref_ &pref.Preferences) Eval { return Eval{ @@ -14,6 +15,38 @@ pub fn new_eval(table &ast.Table, pref_ &pref.Preferences) Eval { } } +// Host API + +pub fn create() Eval { + t := ast.new_table() + mut p, _ := pref.parse_args([], ['interpret', '']) + p.is_script = true + return new_eval(t, p) +} + +pub fn (mut e Eval) push_val(val Object) { + e.stack_vals << val +} + +pub fn (mut e Eval) add_file(filepath string) { + e.user_files << filepath +} + +pub fn (mut e Eval) run(expression string, args ...Object) ![]Object { + mut prepend := 'fn host_pop() voidptr { return 0 }\n' + mut b := builder.new_builder(e.pref) + e.table = b.table + + mut files := b.get_builtin_files() + files << e.user_files + b.set_module_lookup_paths() + + b.interpret_text(prepend + expression, files)! + e.register_symbols(mut b.parsed_files) + e.run_func(e.mods['main']['main'] or { ast.FnDecl{} } as ast.FnDecl, ...args) + return e.return_values +} + // const/global is `Object` type Symbol = Object | ast.EmptyStmt | ast.FnDecl @@ -25,7 +58,9 @@ pub mut: future_register_consts map[string]map[string]map[string]ast.ConstField // mod:file:name:field local_vars map[string]Var local_vars_stack []map[string]Var - scope_idx int // this is increased when e.open_scope() is called, decreased when e.close_scope() (and all variables with that scope level deleted) + stack_vals []Object // host stack popped by host_pop() on interpreted code + user_files []string // user additional files + scope_idx int // this is increased when e.open_scope() is called, decreased when e.close_scope() (and all variables with that scope level deleted) returning bool return_values []Object cur_mod string @@ -60,9 +95,10 @@ pub fn (mut e Eval) run_func(func ast.FnDecl, _args ...Object) { e.cur_file = old_file e.back_trace.pop() } + is_main := func.name == 'main.main' // mut args := _args.clone() - if func.params.len != args.len && !func.is_variadic { + if !is_main && func.params.len != args.len && !func.is_variadic { e.error('mismatched parameter length for ${func.name}: got `${args.len}`, expected `${func.params.len}`') } @@ -94,10 +130,13 @@ pub fn (mut e Eval) run_func(func ast.FnDecl, _args ...Object) { e.open_scope() // have to do this because of cgen error args__ := if func.is_method { args[1..] } else { args } - for i, arg in args__ { - e.local_vars[(func.params[i]).name] = Var{ - val: arg - scope_idx: e.scope_idx + if !is_main { + for i, arg in args__ { + var_name := (func.params[i]).name + e.local_vars[var_name] = Var{ + val: arg + scope_idx: e.scope_idx + } } } if func.is_method { @@ -180,7 +219,6 @@ pub fn (mut e Eval) register_symbol(stmt ast.Stmt, mod string, file string) { } } ast.ExprStmt { - println('expr') x := stmt.expr match x { ast.IfExpr { @@ -190,7 +228,8 @@ pub fn (mut e Eval) register_symbol(stmt ast.Stmt, mod string, file string) { for i, branch in x.branches { mut do_if := false println('branch:${branch}') - match branch.cond { + cond := branch.cond + match cond { ast.Ident { match (branch.cond as ast.Ident).name { 'windows' { diff --git a/vlib/v/eval/expr.v b/vlib/v/eval/expr.v index b123661f08..fde1fe2604 100644 --- a/vlib/v/eval/expr.v +++ b/vlib/v/eval/expr.v @@ -29,6 +29,9 @@ pub fn (mut e Eval) expr(expr ast.Expr, expecting ast.Type) Object { if expr.name == 'int' { e.error('methods not supported') } + if expr.name == 'main.host_pop' { + return e.stack_vals.pop() + } mut args := expr.args.map(e.expr(it.expr, it.typ)) if expr.is_method { args.prepend(e.expr(expr.left, expr.receiver_type)) diff --git a/vlib/v/eval/tests/push_val_test.v b/vlib/v/eval/tests/push_val_test.v new file mode 100644 index 0000000000..4660af5c9a --- /dev/null +++ b/vlib/v/eval/tests/push_val_test.v @@ -0,0 +1,41 @@ +import v.eval + +fn test_pushval() { + mut e := eval.create() + + e.push_val(i64(2)) + e.push_val(f64(2.2)) + e.push_val('calc') + + ret := e.run('println("\${host_pop()}: \${i64(host_pop())+i64(host_pop())}")')! + assert ret == [] +} + +fn test_ret() { + mut e := eval.create() + + e.push_val(i64(2)) + e.push_val(f64(2.2)) + e.push_val('calc') + + ret := e.run('fn test() string { return "\${host_pop()}: \${i64(host_pop())+i64(host_pop())}" } test()')! + assert ret[0].string() == 'calc: 4' +} + +fn test_reuse_stack() { + mut e := eval.create() + + e.push_val(i64(1)) + e.push_val(i64(2)) + + e.push_val(i64(3)) + e.push_val(i64(4)) + + code := 'fn calc(x int, y int) int { return x + y } calc(host_pop(), host_pop())' + + ret := e.run(code)! + assert ret[0].string() == '7' + + ret2 := e.run(code)! + assert ret2[0].string() == '3' +} diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index eb237e9654..f90cb0b30f 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -866,11 +866,13 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin res.path = args[command_pos + 1] res.run_args = args[command_pos + 2..] - must_exist(res.path) - if !res.path.ends_with('.v') && os.is_executable(res.path) && os.is_file(res.path) - && os.is_file(res.path + '.v') { - eprintln('It looks like you wanted to run "${res.path}.v", so we went ahead and did that since "${res.path}" is an executable.') - res.path += '.v' + if res.path != '' { + must_exist(res.path) + if !res.path.ends_with('.v') && os.is_executable(res.path) && os.is_file(res.path) + && os.is_file(res.path + '.v') { + eprintln('It looks like you wanted to run "${res.path}.v", so we went ahead and did that since "${res.path}" is an executable.') + res.path += '.v' + } } } if command == 'build-module' {