module eval import os import os.cmdline as host_cmdline import strconv import time import v2.ast import v2.parser import v2.pref import v2.token type Value = ArrayValue | FlagsValue | MapValue | ModuleValue | RangeValue | StructValue | TupleValue | TypeValue | VoidValue | bool | f64 | i64 | string fn value_f64_payload(value Value) f64 { data := unsafe { (&u64(&value))[1] } if data == 0 { return 0.0 } return unsafe { *(&f64(voidptr(data))) } } struct ArrayValue { mut: elem_type_name string values []Value } struct FlagsValue {} struct MapEntry { key Value value Value } struct MapValue { default_value Value mut: entries []MapEntry } struct ModuleValue { name string } struct RangeValue { start i64 end i64 inclusive bool } struct StructValue { type_name string mut: fields map[string]Value } struct TupleValue { values []Value } struct TypeValue { name string } struct VoidValue {} struct FunctionDef { decl ast.FnDecl file_name string } struct ConstEntry { expr ast.Expr file_name string mut: cached bool evaluating bool value Value } struct SumTypeInfo { module_name string name string mut: variant_tags map[string]int tag_field_aliases map[int][]string } struct InferredSumTypeVariant { tag int variant_name string } struct WrappedSumTypeVariant { tag int payload Value } struct ScopeFrame { mut: vars map[string]Value defers [][]ast.Stmt } struct CallFrame { module_name string file_name string fn_name string } struct MaybeValue { found bool value Value } struct MaybeCallResult { found bool result CallResult } struct MaybeFunctionTarget { found bool module_name string fn_name string } struct CallResult { values []Value mut: mut_args map[int]Value } enum FlowKind { normal break_ continue_ goto_ return_ } struct FlowSignal { kind FlowKind label string values []Value } fn void_value() Value { return VoidValue{} } fn wrap_result_ok(value Value) Value { return StructValue{ type_name: 'Result' fields: { 'is_error': Value(false) 'err': Value('') 'data': value } } } fn wrap_result_err(message string) Value { return StructValue{ type_name: 'Result' fields: { 'is_error': Value(true) 'err': Value(message) 'data': void_value() } } } fn wrap_option_ok(value Value) Value { return StructValue{ type_name: 'Option' fields: { 'state': Value(i64(0)) 'err': Value('') 'data': value } } } fn wrap_option_none() Value { return StructValue{ type_name: 'Option' fields: { 'state': Value(i64(1)) 'err': Value('') 'data': void_value() } } } fn os_result_value(result os.Result) Value { return StructValue{ type_name: 'os.Result' fields: { 'exit_code': Value(i64(result.exit_code)) 'output': Value(result.output) } } } fn is_option_value(value Value) bool { return value is StructValue && 'state' in value.fields && 'err' in value.fields && 'data' in value.fields } fn is_result_value(value Value) bool { return value is StructValue && 'is_error' in value.fields && 'err' in value.fields && 'data' in value.fields } // Eval interprets the v2 AST directly for a limited subset of V. pub struct Eval { pub mut: capture_output bool prefs pref.Preferences mut: stdout_data string stderr_data string functions map[string]map[string]FunctionDef consts map[string]map[string]ConstEntry sum_types map[string]SumTypeInfo struct_field_types map[string]map[string]map[string]ast.Expr struct_embeds map[string]map[string][]string type_kinds map[string]map[string]string type_names map[string]map[string]bool file_import_alias map[string]map[string]string modules map[string]bool scopes []ScopeFrame call_stack []CallFrame next_token_pos_id i64 } // new returns a new evaluator configured for direct execution. pub fn new(prefs_ &pref.Preferences) Eval { return Eval{ capture_output: false prefs: *prefs_ } } // create returns a capturing evaluator convenient for tests. pub fn create() Eval { return Eval{ capture_output: true prefs: pref.new_preferences() } } // stdout returns the captured stdout stream. pub fn (e &Eval) stdout() string { return e.stdout_data } // stderr returns the captured stderr stream. pub fn (e &Eval) stderr() string { return e.stderr_data } // run_text parses and executes a single V source string. pub fn (mut e Eval) run_text(code string) ![]Value { tmp_file := os.join_path(os.temp_dir(), 'v2_eval_${os.getpid()}_${time.now().unix_micro()}.v') os.write_file(tmp_file, code)! defer { os.rm(tmp_file) or {} } mut file_set := token.FileSet.new() mut par := parser.Parser.new(&e.prefs) files := par.parse_files([tmp_file], mut file_set) return e.run_files(files) } // run_files executes parsed AST files and invokes `main.main`. pub fn (mut e Eval) run_files(files []ast.File) ![]Value { e.reset() e.register_files(files)! return e.call_function('main', 'main', []Value{})!.values } fn (mut e Eval) reset() { e.stdout_data = '' e.stderr_data = '' e.functions = map[string]map[string]FunctionDef{} e.consts = map[string]map[string]ConstEntry{} e.sum_types = map[string]SumTypeInfo{} e.struct_field_types = map[string]map[string]map[string]ast.Expr{} e.struct_embeds = map[string]map[string][]string{} e.type_kinds = map[string]map[string]string{} e.type_names = map[string]map[string]bool{} e.file_import_alias = map[string]map[string]string{} e.modules = map[string]bool{} e.scopes = []ScopeFrame{} e.call_stack = []CallFrame{} e.next_token_pos_id = 0 } fn (mut e Eval) register_files(files []ast.File) ! { for file in files { e.modules[file.mod] = true mut import_aliases := map[string]string{} for imp in file.imports { import_aliases[imp.alias] = imp.name.all_after_last('.') } e.file_import_alias[file.name] = import_aliases.clone() if file.mod !in e.functions { e.functions[file.mod] = map[string]FunctionDef{} } if file.mod !in e.consts { e.consts[file.mod] = map[string]ConstEntry{} } if file.mod !in e.type_names { e.type_names[file.mod] = map[string]bool{} } if file.mod !in e.struct_field_types { e.struct_field_types[file.mod] = map[string]map[string]ast.Expr{} } if file.mod !in e.struct_embeds { e.struct_embeds[file.mod] = map[string][]string{} } if file.mod !in e.type_kinds { e.type_kinds[file.mod] = map[string]string{} } for stmt in file.stmts { match stmt { ast.ConstDecl { for field in stmt.fields { key := if field.name.contains('.') { field.name.all_after_last('.') } else { field.name } e.consts[file.mod][key] = ConstEntry{ expr: field.value file_name: file.name value: void_value() } } } ast.EnumDecl { e.type_names[file.mod][stmt.name] = true e.type_kinds[file.mod][stmt.name] = 'Enum' is_flag := stmt.attributes.has('flag') mut next_value := if is_flag { i64(1) } else { i64(0) } for field in stmt.fields { key := field.name value := if field.value is ast.EmptyExpr { Value(next_value) } else if field.value is ast.BasicLiteral && field.value.kind == .number { Value(strconv.parse_int(field.value.value, 0, 64)!) } else { Value(next_value) } int_value := e.value_as_int(value)! e.consts[file.mod][key] = ConstEntry{ expr: ast.empty_expr file_name: file.name cached: true value: int_value } next_value = if is_flag { if int_value <= 0 { i64(1) } else { int_value * 2 } } else { int_value + 1 } } } ast.InterfaceDecl { e.type_names[file.mod][stmt.name] = true e.type_kinds[file.mod][stmt.name] = 'Interface' } ast.FnDecl { e.functions[file.mod][stmt.name] = FunctionDef{ decl: stmt file_name: file.name } if stmt.is_method { receiver_type_name := stmt.receiver.typ.name() if receiver_type_name != '' && receiver_type_name != 'EmptyExpr' { e.functions[file.mod]['${receiver_type_name}__${stmt.name}'] = FunctionDef{ decl: stmt file_name: file.name } short_receiver_name := receiver_type_name.all_after_last('.') if short_receiver_name != receiver_type_name { e.functions[file.mod]['${short_receiver_name}__${stmt.name}'] = FunctionDef{ decl: stmt file_name: file.name } } normalized_receiver_name := receiver_type_name.trim_left('&').trim_right('*') if normalized_receiver_name != '' && normalized_receiver_name != receiver_type_name { e.functions[file.mod]['${normalized_receiver_name}__${stmt.name}'] = FunctionDef{ decl: stmt file_name: file.name } short_normalized_name := normalized_receiver_name.all_after_last('.') if short_normalized_name != normalized_receiver_name { e.functions[file.mod]['${short_normalized_name}__${stmt.name}'] = FunctionDef{ decl: stmt file_name: file.name } } } } } } ast.StructDecl { e.type_names[file.mod][stmt.name] = true e.type_kinds[file.mod][stmt.name] = 'Struct' mut field_types := map[string]ast.Expr{} for field in stmt.fields { field_types[field.name] = field.typ } e.struct_field_types[file.mod][stmt.name] = field_types.clone() e.struct_embeds[file.mod][stmt.name] = stmt.embedded.map(e.type_expr_name(it)) } ast.TypeDecl { e.type_names[file.mod][stmt.name] = true if stmt.variants.len > 0 { e.type_kinds[file.mod][stmt.name] = 'SumType' e.register_sum_type(file.mod, stmt) } else { e.type_kinds[file.mod][stmt.name] = 'Alias' } } else {} } } } if 'main' !in e.functions || 'main' !in e.functions['main'] { return error('v2.eval: missing main.main entry point') } } fn add_type_name_alias(mut aliases []string, alias string) { if alias != '' && alias !in aliases { aliases << alias } } fn (e &Eval) type_name_aliases(module_name string, type_name string) []string { mut aliases := []string{} if type_name == '' { return aliases } add_type_name_alias(mut aliases, type_name) if type_name.starts_with('[]') { for inner_alias in e.type_name_aliases(module_name, type_name[2..]) { add_type_name_alias(mut aliases, '[]${inner_alias}') } return aliases } if type_name.starts_with('map[') { inner := type_name[4..] if bracket_idx := inner.index(']') { key_name := inner[..bracket_idx] value_name := inner[bracket_idx + 1..] key_aliases := e.type_name_aliases(module_name, key_name) value_aliases := e.type_name_aliases(module_name, value_name) for key_alias in key_aliases { for value_alias in value_aliases { add_type_name_alias(mut aliases, 'map[${key_alias}]${value_alias}') } } } return aliases } if type_name.contains('__') { add_type_name_alias(mut aliases, type_name.replace('__', '.')) add_type_name_alias(mut aliases, type_name.all_after_last('__')) return aliases } if type_name.contains('.') { add_type_name_alias(mut aliases, type_name.replace('.', '__')) add_type_name_alias(mut aliases, type_name.all_after_last('.')) return aliases } if module_name != '' { add_type_name_alias(mut aliases, '${module_name}.${type_name}') add_type_name_alias(mut aliases, '${e.mangled_module_name(module_name)}__${type_name}') } return aliases } fn (e &Eval) sumtype_variant_field_name(variant_name string) string { if variant_name.starts_with('[]') { return 'Array_${variant_name[2..].replace('.', '__')}' } if variant_name.starts_with('map[') { inner := variant_name[4..] if bracket_idx := inner.index(']') { key := inner[..bracket_idx].replace('.', '__') val := inner[bracket_idx + 1..].replace('.', '__') return 'Map_${key}_${val}' } } if variant_name.contains('__') { return variant_name.all_after_last('__') } if variant_name.contains('.') { return variant_name.all_after_last('.') } return variant_name } fn (mut e Eval) register_sum_type(module_name string, decl ast.TypeDecl) { mut info := SumTypeInfo{ module_name: module_name name: decl.name variant_tags: map[string]int{} tag_field_aliases: map[int][]string{} } for tag, variant in decl.variants { variant_name := e.type_expr_name(variant) for alias in e.type_name_aliases(module_name, variant_name) { info.variant_tags[alias] = tag field_alias := '_' + e.sumtype_variant_field_name(alias) mut field_aliases := []string{} if tag in info.tag_field_aliases { field_aliases = info.tag_field_aliases[tag] } if field_alias !in field_aliases { field_aliases << field_alias } info.tag_field_aliases[tag] = field_aliases } } for alias in e.type_name_aliases(module_name, decl.name) { e.sum_types[alias] = info } } fn is_composite_sumtype_lookup_name(name string) bool { return name.starts_with('[]') || name.starts_with('map[') || (name.starts_with('[') && name.contains(']')) || name.starts_with('?') || name.starts_with('!') || name.starts_with('chan ') || name.starts_with('thread ') } fn (e &Eval) sum_type_info(type_name string) ?SumTypeInfo { trimmed := type_name.trim_left('&').trim_right('*') if trimmed in e.sum_types { return e.sum_types[trimmed] } if is_composite_sumtype_lookup_name(trimmed) { return none } if trimmed.contains('.') { short_name := trimmed.all_after_last('.') if short_name in e.sum_types { return e.sum_types[short_name] } } if trimmed.contains('__') { short_name := trimmed.all_after_last('__') if short_name in e.sum_types { return e.sum_types[short_name] } dotted := trimmed.replace('__', '.') if dotted in e.sum_types { return e.sum_types[dotted] } } return none } fn (e &Eval) is_sum_type_name(type_name string) bool { _ = e.sum_type_info(type_name) or { return false } return true } fn (e &Eval) lookup_sumtype_variant_tag(info SumTypeInfo, variant_name string) ?int { for alias in e.type_name_aliases(info.module_name, variant_name) { if alias in info.variant_tags { return info.variant_tags[alias] } } return none } fn builtin_type_kind_alias(name string) ?string { trimmed := name.all_after_last('.') return match trimmed { 'bool', 'f32', 'f64', 'float_literal', 'i8', 'i16', 'i32', 'int', 'i64', 'int_literal', 'u8', 'byte', 'u16', 'u32', 'u64' { 'Primitive' } 'byteptr', 'charptr', 'voidptr' { 'Alias' } 'char' { 'Char' } 'isize' { 'ISize' } 'nil' { 'Nil' } 'none' { 'None' } 'rune' { 'Rune' } 'string' { 'String' } 'usize' { 'USize' } 'void' { 'Void' } else { none } } } fn (e &Eval) registered_type_kind(type_name string) ?string { for alias in struct_field_lookup_candidates(type_name) { mut module_name := '' mut short_type_name := alias if alias.contains('.') { module_name = alias.all_before_last('.') short_type_name = alias.all_after_last('.') } if module_name != '' { if module_kinds := e.type_kinds[module_name] { if short_type_name in module_kinds { return module_kinds[short_type_name] or { return none } } } continue } current_module := e.current_module_name() if module_kinds := e.type_kinds[current_module] { if short_type_name in module_kinds { return module_kinds[short_type_name] or { return none } } } for module_kinds in e.type_kinds.values() { if short_type_name in module_kinds { return module_kinds[short_type_name] or { return none } } } } return none } fn (e &Eval) type_value_variant_aliases(info SumTypeInfo, value TypeValue) []string { mut aliases := e.type_name_aliases(info.module_name, value.name) if type_kind := e.registered_type_kind(value.name) { add_type_name_alias(mut aliases, type_kind) } if builtin_kind := builtin_type_kind_alias(value.name) { add_type_name_alias(mut aliases, builtin_kind) } add_type_name_alias(mut aliases, 'Type') return aliases } fn (e &Eval) canonical_sumtype_variant_name(info SumTypeInfo, alias string) string { if alias.contains('.') { return alias } if alias.contains('__') { dotted := alias.replace('__', '.') if dotted.contains('.') { return dotted } } short_name := alias.all_after_last('__') if info.module_name != '' && short_name != '' { return '${info.module_name}.${short_name}' } if short_name != '' { return short_name } return alias } fn (e &Eval) lookup_struct_field_names(type_name string) ?[]string { for candidate in struct_field_lookup_candidates(type_name) { mut module_name := '' mut short_type_name := candidate if candidate.contains('.') { module_name = candidate.all_before_last('.') short_type_name = candidate.all_after_last('.') } if module_name != '' { if module_fields := e.struct_field_types[module_name] { if type_fields := module_fields[short_type_name] { return type_fields.keys() } } continue } current_module := e.current_module_name() if module_fields := e.struct_field_types[current_module] { if type_fields := module_fields[short_type_name] { return type_fields.keys() } } for _, module_fields in e.struct_field_types { if type_fields := module_fields[short_type_name] { return type_fields.keys() } } } return none } fn (e &Eval) zero_value_for_type_name(type_name string) Value { if type_name == '' { return void_value() } if type_name.starts_with('[]') { return ArrayValue{} } if type_name.starts_with('map[') { inner := type_name[4..] if bracket_idx := inner.index(']') { return MapValue{ default_value: e.zero_value_for_type_name(inner[bracket_idx + 1..]) } } return MapValue{} } if type_name.starts_with('?') || type_name.starts_with('!') { return void_value() } match type_name { 'bool' { return false } 'byte', 'char', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'u8', 'u16', 'u32', 'u64', 'usize' { return i64(0) } 'f32', 'f64' { return f64(0.0) } 'string' { return '' } else { return e.zero_struct_value(type_name) } } } fn (e &Eval) zero_value_for_sumtype_data_field(data_type_name string, field_name string) ?Value { if !data_type_name.ends_with('._data') || !field_name.starts_with('_') { return none } parent_sumtype_name := data_type_name.all_before_last('._data') info := e.sum_type_info(parent_sumtype_name) or { return none } mut variant_alias := '' for alias in info.variant_tags.keys() { if '_' + e.sumtype_variant_field_name(alias) == field_name { variant_alias = e.canonical_sumtype_variant_name(info, alias) break } } if variant_alias == '' { return none } return e.zero_value_for_type_name(variant_alias) } fn (e &Eval) sumtype_variant_field_value(sum_value StructValue, field_name string) ?Value { if !field_name.starts_with('_') { return none } info := e.sum_type_info(sum_value.type_name) or { return none } tag_value := sum_value.fields['_tag'] or { return none } tag := int(e.value_as_int(tag_value) or { return none }) mut variant_alias := '' mut variant_tag := -1 for alias, alias_tag in info.variant_tags { if '_' + e.sumtype_variant_field_name(alias) == field_name { variant_alias = e.canonical_sumtype_variant_name(info, alias) variant_tag = alias_tag break } } if variant_alias == '' { return none } if tag == variant_tag { if payload := e.unwrap_sumtype_value(sum_value, variant_alias) { if !is_semantically_empty_value(payload) { return payload } } } return e.zero_value_for_type_name(variant_alias) } fn (e &Eval) infer_bare_sumtype_variant(info SumTypeInfo, value StructValue) ?InferredSumTypeVariant { if '_tag' in value.fields || '_data' in value.fields { return none } if value.fields.len == 0 { return none } mut matches := map[int]string{} for alias, tag in info.variant_tags { field_names := e.lookup_struct_field_names(alias) or { continue } if field_names.len != value.fields.len { continue } mut all_fields_match := true for field_name in value.fields.keys() { if field_name !in field_names { all_fields_match = false break } } if !all_fields_match { continue } if tag !in matches { matches[tag] = e.canonical_sumtype_variant_name(info, alias) } } if matches.len != 1 { return none } for tag, variant_name in matches { return InferredSumTypeVariant{ tag: tag variant_name: variant_name } } return none } fn (e &Eval) infer_nested_sumtype_variant(info SumTypeInfo, value Value) ?WrappedSumTypeVariant { for alias, tag in info.variant_tags { nested_info := e.sum_type_info(alias) or { continue } if nested_info.name == info.name && nested_info.module_name == info.module_name { continue } nested_payload := e.cast_value(value, alias) or { continue } match nested_payload { StructValue { if e.type_name_matches(nested_payload.type_name, alias) && '_tag' in nested_payload.fields && '_data' in nested_payload.fields { return WrappedSumTypeVariant{ tag: tag payload: nested_payload } } } else {} } } return none } fn (e &Eval) sumtype_tag_from_value(info SumTypeInfo, value Value) ?int { match value { StructValue { if e.type_name_matches(value.type_name, '${info.module_name}.${info.name}') { if tag_value := value.fields['_tag'] { return int(e.value_as_int(tag_value) or { return none }) } } for alias in e.type_name_aliases(info.module_name, value.type_name) { if alias in info.variant_tags { return info.variant_tags[alias] } } if inferred := e.infer_bare_sumtype_variant(info, value) { return inferred.tag } } string { return e.lookup_sumtype_variant_tag(info, 'string') } bool { return e.lookup_sumtype_variant_tag(info, 'bool') } i64 { for alias in ['int', 'i64', 'i32', 'i16', 'i8', 'u64', 'u32', 'u16', 'u8', 'byte', 'char', 'rune'] { if tag := e.lookup_sumtype_variant_tag(info, alias) { return tag } } } f64 { for alias in ['f64', 'f32'] { if tag := e.lookup_sumtype_variant_tag(info, alias) { return tag } } } ArrayValue { mut tags := []int{} for alias, tag in info.variant_tags { if alias.starts_with('[]') && tag !in tags { tags << tag } } if tags.len == 1 { return tags[0] } } MapValue { mut tags := []int{} for alias, tag in info.variant_tags { if alias.starts_with('map[') && tag !in tags { tags << tag } } if tags.len == 1 { return tags[0] } } TypeValue { for alias in e.type_value_variant_aliases(info, value) { if alias in info.variant_tags { return info.variant_tags[alias] } } } else {} } if nested := e.infer_nested_sumtype_variant(info, value) { return nested.tag } return none } fn (e &Eval) lookup_const_expr(module_name string, name string) ?ast.Expr { if module_name !in e.consts { return none } entry := e.consts[module_name][name] or { return none } if entry.expr is ast.EmptyExpr { return none } return entry.expr } fn (e &Eval) sumtype_variant_name_from_expr(expr ast.Expr) ?string { return e.sumtype_variant_name_from_expr_with_depth(expr, 0) } fn (e &Eval) sumtype_variant_name_from_expr_with_depth(expr ast.Expr, depth int) ?string { if depth > 8 { return none } return match expr { ast.ArrayInitExpr { if expr.typ is ast.EmptyExpr { none } else { e.type_expr_name(expr.typ) } } ast.BasicLiteral { match expr.kind { .key_false, .key_true { 'bool' } .char { 'char' } .number { if expr.value.contains('.') || expr.value.contains('e') || expr.value.contains('E') { 'f64' } else { 'int' } } else { none } } } ast.CallOrCastExpr { if e.is_type_expr(expr.lhs) { e.type_expr_name(expr.lhs) } else { none } } ast.CastExpr { e.type_expr_name(expr.typ) } ast.InitExpr { e.type_expr_name(expr.typ) } ast.Ident { cur_module := e.current_module_name() if const_expr := e.lookup_const_expr(cur_module, expr.name) { e.sumtype_variant_name_from_expr_with_depth(const_expr, depth + 1) } else { if const_expr2 := e.lookup_const_expr('builtin', expr.name) { e.sumtype_variant_name_from_expr_with_depth(const_expr2, depth + 1) } else { none } } } ast.MapInitExpr { if expr.typ is ast.EmptyExpr { none } else { e.type_expr_name(expr.typ) } } ast.SelectorExpr { if expr.lhs is ast.Ident { module_name := e.resolve_module_name(expr.lhs.name) if module_name != '' { if const_expr := e.lookup_const_expr(module_name, expr.rhs.name) { e.sumtype_variant_name_from_expr_with_depth(const_expr, depth + 1) } else { none } } else { none } } else { none } } ast.StringInterLiteral, ast.StringLiteral { 'string' } else { none } } } fn (e &Eval) build_sumtype_wrapper(type_name string, info SumTypeInfo, tag int, payload Value) StructValue { mut data_fields := map[string]Value{} mut field_aliases := []string{} if tag in info.tag_field_aliases { field_aliases = info.tag_field_aliases[tag] } for field_alias in field_aliases { data_fields[field_alias] = payload } return StructValue{ type_name: type_name fields: { '_tag': Value(i64(tag)) '_data': Value(StructValue{ type_name: '${type_name}._data' fields: data_fields }) } } } fn (e &Eval) wrap_sumtype_value(sum_type_name string, value Value, expr ast.Expr) !Value { if value is StructValue && value.type_name == sum_type_name && '_tag' in value.fields && '_data' in value.fields { return value } info := e.sum_type_info(sum_type_name) or { return error('v2.eval: unknown sum type `${sum_type_name}`') } if value is StructValue { if inferred := e.infer_bare_sumtype_variant(info, value) { payload := StructValue{ type_name: inferred.variant_name fields: value.fields.clone() } return e.build_sumtype_wrapper(sum_type_name, info, inferred.tag, payload) } } if nested := e.infer_nested_sumtype_variant(info, value) { return e.build_sumtype_wrapper(sum_type_name, info, nested.tag, nested.payload) } if variant_name := e.sumtype_variant_name_from_expr(expr) { if tag := e.lookup_sumtype_variant_tag(info, variant_name) { return e.build_sumtype_wrapper(sum_type_name, info, tag, value) } } if tag := e.sumtype_tag_from_value(info, value) { return e.build_sumtype_wrapper(sum_type_name, info, tag, value) } return error('v2.eval: can not wrap `${e.runtime_type_name(value)}` into `${sum_type_name}` in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } fn (e &Eval) unwrap_sumtype_value(sum_value StructValue, target_type string) ?Value { info := e.sum_type_info(sum_value.type_name) or { return none } tag_value := sum_value.fields['_tag'] or { return none } tag := int(e.value_as_int(tag_value) or { return none }) target_tag := e.lookup_sumtype_variant_tag(info, target_type) or { return none } if tag != target_tag { return none } data_value := sum_value.fields['_data'] or { return none } if data_value !is StructValue { return none } data_struct := data_value as StructValue mut field_aliases := []string{} if tag in info.tag_field_aliases { field_aliases = info.tag_field_aliases[tag] } for field_alias in field_aliases { for key, payload in data_struct.fields { if key == field_alias { return payload } } } for _, payload in data_struct.fields { return payload } return none } fn (e &Eval) current_module_name() string { if e.call_stack.len == 0 { return 'main' } return e.call_stack[e.call_stack.len - 1].module_name } fn (e &Eval) current_file_name() string { if e.call_stack.len == 0 { return '' } return e.call_stack[e.call_stack.len - 1].file_name } fn (e &Eval) current_function_label() string { if e.call_stack.len == 0 { return 'main.main' } frame := e.call_stack[e.call_stack.len - 1] return '${frame.module_name}.${frame.fn_name}' } fn (e &Eval) call_stack_trace() string { if e.call_stack.len == 0 { return 'main.main' } return e.call_stack.map('${it.module_name}.${it.fn_name}').join(' <- ') } fn (e &Eval) unknown_variable_error(name string) IError { return error('v2.eval: unknown variable `${name}` in `${e.current_function_label()}`') } fn call_result_value(result CallResult) Value { return if result.values.len == 0 { void_value() } else if result.values.len == 1 { result.values[0] } else { TupleValue{ values: result.values } } } fn (mut e Eval) writeback_mut_args(arg_exprs []ast.Expr, result CallResult) ! { for idx, value in result.mut_args { if idx < 0 || idx >= arg_exprs.len { continue } if can_update_target(arg_exprs[idx]) { e.update_target(arg_exprs[idx], value)! } } } fn (mut e Eval) call_function(module_name string, fn_name string, args []Value) !CallResult { if builtin_call_result := e.maybe_call_builtin_call_result(module_name, fn_name, args) { return builtin_call_result.result } builtin_result := e.maybe_call_builtin_function(module_name, fn_name, args) if builtin_result.found { return CallResult{ values: [builtin_result.value] mut_args: map[int]Value{} } } if target := e.resolve_mangled_function_target(fn_name) { return e.call_function(target.module_name, target.fn_name, args) } if module_name !in e.functions { return error('v2.eval: unknown module `${module_name}`') } def := e.functions[module_name][fn_name] or { return error('v2.eval: unknown function `${module_name}.${fn_name}`') } e.call_stack << CallFrame{ module_name: module_name file_name: def.file_name fn_name: fn_name } defer { e.call_stack.pop() } scope_start := e.scopes.len e.open_scope() defer { for e.scopes.len > scope_start { e.close_scope() or {} } } has_receiver_arg := def.decl.is_method && !def.decl.is_static expected_args := def.decl.typ.params.len + if has_receiver_arg { 1 } else { 0 } if expected_args != args.len { return error('v2.eval: `${module_name}.${fn_name}` expected ${expected_args} arguments, got ${args.len}') } arg_offset := if has_receiver_arg { 1 } else { 0 } if has_receiver_arg && def.decl.receiver.name != '' && def.decl.receiver.name != '_' { receiver_value := if def.decl.receiver.typ is ast.EmptyExpr { args[0] } else { e.adapt_value_to_type(args[0], def.decl.receiver.typ) } e.declare_var(def.decl.receiver.name, receiver_value) } for i, param in def.decl.typ.params { param_value := if param.typ is ast.EmptyExpr { args[i + arg_offset] } else { e.adapt_value_to_type(args[i + arg_offset], param.typ) } e.declare_var(param.name, param_value) } signal := e.exec_stmts(def.decl.stmts)! mut returned_values := []Value{} if signal.kind == .return_ { returned_values = signal.values.clone() } else if signal.kind == .goto_ { return error('v2.eval: unknown goto label `${signal.label}`') } else if signal.kind != .normal { return error('v2.eval: unexpected `${signal.kind}` escaped `${module_name}.${fn_name}`') } if def.decl.typ.return_type is ast.Type { match def.decl.typ.return_type { ast.OptionType { if returned_values.len == 1 && is_option_value(returned_values[0]) { return CallResult{ values: [returned_values[0]] mut_args: map[int]Value{} } } if returned_values.len == 0 || (returned_values.len == 1 && returned_values[0] is VoidValue) { return CallResult{ values: [wrap_option_none()] mut_args: map[int]Value{} } } payload := if returned_values.len == 1 { e.adapt_value_to_type(returned_values[0], def.decl.typ.return_type.base_type) } else { Value(TupleValue{ values: returned_values }) } return CallResult{ values: [wrap_option_ok(payload)] mut_args: map[int]Value{} } } ast.ResultType { if returned_values.len == 1 && is_result_value(returned_values[0]) { return CallResult{ values: [returned_values[0]] mut_args: map[int]Value{} } } payload := if returned_values.len == 0 { void_value() } else if returned_values.len == 1 { e.adapt_value_to_type(returned_values[0], def.decl.typ.return_type.base_type) } else { Value(TupleValue{ values: returned_values }) } return CallResult{ values: [wrap_result_ok(payload)] mut_args: map[int]Value{} } } else {} } } if returned_values.len == 1 && def.decl.typ.return_type !is ast.EmptyExpr { returned_values[0] = e.adapt_value_to_type(returned_values[0], def.decl.typ.return_type) } mut result := CallResult{ values: returned_values mut_args: map[int]Value{} } if has_receiver_arg && def.decl.receiver.is_mut && def.decl.receiver.name != '' && def.decl.receiver.name != '_' { receiver_value := e.lookup_var(def.decl.receiver.name) if receiver_value.found { result.mut_args[0] = receiver_value.value } } for i, param in def.decl.typ.params { if !param.is_mut || param.name == '' || param.name == '_' { continue } param_value := e.lookup_var(param.name) if param_value.found { result.mut_args[i + arg_offset] = param_value.value } } return result } fn (mut e Eval) maybe_call_builtin_call_result(module_name string, fn_name string, args []Value) ?MaybeCallResult { if module_name !in ['', 'builtin'] && !fn_name.starts_with('__Map_') { return none } if map_result := e.maybe_call_map_builtin_result(fn_name, args) { return map_result } return none } fn builtin_map_helper_matches(fn_name string, suffix string) bool { return fn_name == 'map__${suffix}' || fn_name.ends_with('map__${suffix}') || (fn_name.starts_with('__Map_') && fn_name.ends_with('_${suffix}')) } fn mut_call_result(value Value, index int, updated Value) CallResult { mut mut_args := map[int]Value{} mut_args[index] = updated return CallResult{ values: [value] mut_args: mut_args } } fn (mut e Eval) maybe_call_map_builtin_result(fn_name string, args []Value) ?MaybeCallResult { if args.len == 0 || args[0] !is MapValue { return none } receiver := args[0] as MapValue if builtin_map_helper_matches(fn_name, 'set') && args.len >= 3 { updated := e.map_set_value(receiver, args[1], args[2]) return MaybeCallResult{ found: true result: mut_call_result(void_value(), 0, updated) } } if builtin_map_helper_matches(fn_name, 'get') && args.len >= 3 { value, found := e.map_lookup(receiver, args[1]) return MaybeCallResult{ found: true result: CallResult{ values: [if found { value } else { args[2] }] mut_args: map[int]Value{} } } } if builtin_map_helper_matches(fn_name, 'get_and_set') && args.len >= 3 { value, found := e.map_lookup(receiver, args[1]) if found { return MaybeCallResult{ found: true result: CallResult{ values: [value] mut_args: map[int]Value{} } } } updated := e.map_set_value(receiver, args[1], args[2]) return MaybeCallResult{ found: true result: mut_call_result(args[2], 0, updated) } } if builtin_map_helper_matches(fn_name, 'exists') && args.len >= 2 { return MaybeCallResult{ found: true result: CallResult{ values: [e.map_contains_key(receiver, args[1])] mut_args: map[int]Value{} } } } if builtin_map_helper_matches(fn_name, 'delete') && args.len >= 2 { updated := e.map_delete_value(receiver, args[1]) return MaybeCallResult{ found: true result: mut_call_result(void_value(), 0, updated) } } if builtin_map_helper_matches(fn_name, 'keys') { return MaybeCallResult{ found: true result: CallResult{ values: [e.map_keys(receiver)] mut_args: map[int]Value{} } } } if builtin_map_helper_matches(fn_name, 'values') { return MaybeCallResult{ found: true result: CallResult{ values: [e.map_values(receiver)] mut_args: map[int]Value{} } } } if builtin_map_helper_matches(fn_name, 'clone') { return MaybeCallResult{ found: true result: CallResult{ values: [e.map_clone(receiver)] mut_args: map[int]Value{} } } } if builtin_map_helper_matches(fn_name, 'clear') { return MaybeCallResult{ found: true result: mut_call_result(void_value(), 0, e.map_clear(receiver)) } } if builtin_map_helper_matches(fn_name, 'move') { return MaybeCallResult{ found: true result: mut_call_result(receiver, 0, e.map_clear(receiver)) } } return none } fn safe_arg(args []Value, idx int) Value { if idx < args.len { return args[idx] } return void_value() } fn builtin_method_name(fn_name string) string { if !fn_name.contains('__') { return fn_name } return fn_name.all_after_last('__') } fn builtin_receiver_name(fn_name string) string { if !fn_name.contains('__') { return '' } mut prefix := fn_name.all_before_last('__') if prefix.contains('__') { prefix = prefix.all_after_last('__') } return prefix } fn (e &Eval) expect_token_arg(args []Value, index int) !token.Token { raw := e.value_as_int(safe_arg(args, index))! // Enum values are stored as ints in eval, so host-side token helpers need a narrow cast here. return unsafe { token.Token(int(raw)) } } fn (e &Eval) expect_byte_arg(args []Value, index int) !u8 { raw := e.value_as_int(safe_arg(args, index))! return u8(raw) } fn (e &Eval) infer_array_elem_type(values []Value) string { if values.len == 0 { return '' } return e.runtime_type_name(values[0]) } fn (e &Eval) array_elem_type_name(expr ast.Expr) string { return match expr { ast.Type { match expr { ast.ArrayType, ast.ArrayFixedType { e.type_expr_name(expr.elem_type) } else { '' } } } else { type_name := e.type_expr_name(expr) if type_name.starts_with('[]') { type_name[2..] } else if type_name.starts_with('[') && type_name.contains(']') { type_name.all_after(']') } else { '' } } } } fn (e &Eval) annotate_value_for_type(value Value, typ ast.Expr) Value { match value { ArrayValue { elem_type_name := if value.elem_type_name != '' { value.elem_type_name } else { e.array_elem_type_name(typ) } return ArrayValue{ elem_type_name: elem_type_name values: value.values } } else { return value } } } fn (e &Eval) is_existing_sumtype_wrapper(value Value, target_name string) bool { return match value { StructValue { e.type_name_matches(value.type_name, target_name) } else { false } } } fn (e &Eval) adapt_value_to_type_name(value Value, type_name string) Value { if type_name == '' { return value } if type_name.starts_with('?') { if is_option_value(value) { return value } if value is VoidValue { return wrap_option_none() } return wrap_option_ok(e.adapt_value_to_type_name(value, type_name[1..])) } if type_name.starts_with('!') { if is_result_value(value) { return value } return wrap_result_ok(e.adapt_value_to_type_name(value, type_name[1..])) } if type_name.starts_with('[]') && value is ArrayValue { array_value := value as ArrayValue return ArrayValue{ elem_type_name: e.qualify_type_name(e.current_module_name(), type_name[2..]) values: array_value.values } } if e.is_sum_type_name(type_name) { if e.is_existing_sumtype_wrapper(value, type_name) { return value } return e.cast_value(value, type_name) or { value } } match type_name { 'bool' { return e.value_as_bool(value) or { return value } } 'byte', 'char', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'u8', 'u16', 'u32', 'u64', 'usize' { return e.value_as_int(value) or { return value } } 'f32', 'f64' { return e.value_as_f64(value) or { return value } } 'string' { return e.value_string(value) } else { if value is StructValue { return e.cast_value(value, type_name) or { Value(value) } } return value } } } fn (e &Eval) adapt_value_to_type(value Value, typ ast.Expr) Value { adapted := e.annotate_value_for_type(value, typ) if typ is ast.Type { match typ { ast.OptionType { if is_option_value(adapted) { return adapted } if adapted is VoidValue { return wrap_option_none() } return wrap_option_ok(e.adapt_value_to_type(adapted, typ.base_type)) } ast.ResultType { if is_result_value(adapted) { return adapted } return wrap_result_ok(e.adapt_value_to_type(adapted, typ.base_type)) } else {} } } target_name := e.type_expr_name(typ) if target_name != '' && e.is_sum_type_name(target_name) { if e.is_existing_sumtype_wrapper(adapted, target_name) { return adapted } return e.cast_value(adapted, target_name) or { adapted } } return adapted } fn (e &Eval) should_append_many(container ArrayValue, value ArrayValue) bool { if container.elem_type_name != '' && value.elem_type_name != '' { return container.elem_type_name == value.elem_type_name } if container.elem_type_name != '' && value.values.len > 0 { return container.elem_type_name == e.runtime_type_name(value.values[0]) } if container.values.len > 0 && value.values.len > 0 { return e.runtime_type_name(container.values[0]) == e.runtime_type_name(value.values[0]) } return false } fn (e &Eval) inferred_sumtype_tag(value Value) ?int { mut matches := map[string]int{} for _, info in e.sum_types { if tag := e.sumtype_tag_from_value(info, value) { key := '${info.module_name}.${info.name}:${tag}' matches[key] = tag } } if matches.len != 1 { return none } for _, tag in matches { return tag } return none } fn (mut e Eval) contextual_sumtype_tag(target_expr ast.Expr, value Value) ?int { match target_expr { ast.SelectorExpr { base := e.eval_expr(target_expr.lhs) or { return none } if base is StructValue { if field_type := e.lookup_struct_field_type(base.type_name, target_expr.rhs.name) { target_name := e.type_expr_name(field_type) if info := e.sum_type_info(target_name) { return e.sumtype_tag_from_value(info, value) } } } } ast.IndexExpr { container := e.eval_expr(target_expr.lhs) or { return none } match container { ArrayValue { if info := e.sum_type_info(container.elem_type_name) { return e.sumtype_tag_from_value(info, value) } } MapValue { if container.default_value is StructValue { default_value := container.default_value as StructValue if info := e.sum_type_info(default_value.type_name) { return e.sumtype_tag_from_value(info, value) } } } else {} } } else {} } return none } fn eval_token_method_value(tok token.Token, method_name string) ?Value { return match method_name { 'str' { Value(tok.str()) } 'left_binding_power' { Value(i64(int(tok.left_binding_power()))) } 'right_binding_power' { Value(i64(int(tok.right_binding_power()))) } 'is_keyword' { Value(tok.is_keyword()) } 'is_prefix' { Value(tok.is_prefix()) } 'is_infix' { Value(tok.is_infix()) } 'is_postfix' { Value(tok.is_postfix()) } 'is_assignment' { Value(tok.is_assignment()) } 'is_overloadable' { Value(tok.is_overloadable()) } 'is_comparison' { Value(tok.is_comparison()) } else { none } } } fn eval_byte_method_value(c u8, method_name string) ?Value { return match method_name { 'is_space' { Value(c == 32 || (c > 8 && c < 14) || c == 0x85 || c == 0xa0) } 'is_digit' { Value(c >= `0` && c <= `9`) } 'is_hex_digit' { Value((c >= `0` && c <= `9`) || (c >= `a` && c <= `f`) || (c >= `A` && c <= `F`)) } 'is_oct_digit' { Value(c >= `0` && c <= `7`) } 'is_bin_digit' { Value(c == `0` || c == `1`) } 'is_letter' { Value((c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)) } 'is_alnum' { Value((c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`)) } 'is_capital' { Value(c >= `A` && c <= `Z`) } else { none } } } fn (e &Eval) maybe_call_token_builtin(fn_name string, args []Value) MaybeValue { method_name := builtin_method_name(fn_name) receiver_name := builtin_receiver_name(fn_name) if receiver_name == 'Token' { tok := e.expect_token_arg(args, 0) or { return MaybeValue{} } if value := eval_token_method_value(tok, method_name) { return MaybeValue{ found: true value: value } } return MaybeValue{} } if method_name == 'from_string_tinyv' && receiver_name in ['', 'Token'] { name := e.expect_string_arg(args, args.len - 1) or { return MaybeValue{} } return MaybeValue{ found: true value: i64(int(token.Token.from_string_tinyv(name))) } } return MaybeValue{} } fn (e &Eval) maybe_call_ast_builtin(fn_name string, args []Value) MaybeValue { method_name := builtin_method_name(fn_name) receiver_name := builtin_receiver_name(fn_name) if receiver_name in ['', 'StringLiteralKind'] { match method_name { 'str' { raw := e.value_as_int(safe_arg(args, 0)) or { return MaybeValue{} } // StringLiteralKind is stored as an int in eval and only used with known enum values here. kind := unsafe { ast.StringLiteralKind(int(raw)) } return MaybeValue{ found: true value: kind.str() } } 'from_string_tinyv' { name := e.expect_string_arg(args, args.len - 1) or { return MaybeValue{} } return MaybeValue{ found: true value: i64(int(ast.StringLiteralKind.from_string_tinyv(name))) } } else {} } } return MaybeValue{} } fn (e &Eval) maybe_call_byte_builtin(module_name string, fn_name string, args []Value) MaybeValue { if module_name !in ['', 'builtin'] { return MaybeValue{} } receiver_name := builtin_receiver_name(fn_name) if receiver_name !in ['u8', 'byte'] { return MaybeValue{} } c := e.expect_byte_arg(args, 0) or { return MaybeValue{} } if value := eval_byte_method_value(c, builtin_method_name(fn_name)) { return MaybeValue{ found: true value: value } } return MaybeValue{} } fn is_semantically_empty_value(value Value) bool { return match value { VoidValue { true } StructValue { for field_value in value.fields.values() { if !is_semantically_empty_value(field_value) { return false } } true } else { false } } } fn (e &Eval) type_sum_data_is_nil(value Value) bool { match value { StructValue { if '_data' !in value.fields { return false } data_value := value.fields['_data'] or { return false } return is_semantically_empty_value(data_value) } TypeValue { return false } else { return is_semantically_empty_value(value) } } } fn has_flag_bit(bits i64, flag i64) bool { return (bits & flag) != 0 } fn (e &Eval) primitive_type_name(value StructValue) string { props := e.value_as_int(value.fields['props'] or { Value(i64(0)) }) or { 0 } size := e.value_as_int(value.fields['size'] or { Value(i64(0)) }) or { 0 } if has_flag_bit(props, 1) { return 'bool' } if has_flag_bit(props, 16) { if has_flag_bit(props, 4) { return 'int_literal' } if has_flag_bit(props, 2) { return 'float_literal' } } if has_flag_bit(props, 2) { return if size == 32 { 'f32' } else { 'f64' } } if has_flag_bit(props, 4) { if has_flag_bit(props, 8) { return match size { 8 { 'u8' } 16 { 'u16' } 32 { 'u32' } 64 { 'u64' } else { 'u64' } } } return match size { 8 { 'i8' } 16 { 'i16' } 32 { 'i32' } 64 { 'i64' } else { 'int' } } } return 'int' } fn (e &Eval) option_payload_type_name(value Value) string { if !is_option_value(value) || value !is StructValue { return '' } option_value := value as StructValue state := e.value_as_int(option_value.fields['state'] or { Value(i64(1)) }) or { 1 } if state != 0 { return '' } return e.types_value_name(option_value.fields['data'] or { return '' }) } fn (e &Eval) tuple_type_name(value StructValue) string { if types_value := value.fields['types'] { if types_value is ArrayValue { return '(' + types_value.values.map(e.types_value_name(it)).join(',') + ')' } } return '()' } fn (e &Eval) sumtype_payload_name(value StructValue) string { data_value := value.fields['_data'] or { return 'void' } if data_value is StructValue { data_struct := data_value as StructValue if tag_value := value.fields['_tag'] { tag := int(e.value_as_int(tag_value) or { -1 }) if info := e.sum_type_info(value.type_name) { if field_aliases := info.tag_field_aliases[tag] { for field_alias in field_aliases { if payload := data_struct.fields[field_alias] { return e.types_value_name(payload) } } } } } for payload in data_struct.fields.values() { if !is_semantically_empty_value(payload) { return e.types_value_name(payload) } } for payload in data_struct.fields.values() { return e.types_value_name(payload) } } return 'void' } fn (e &Eval) types_value_name(value Value) string { return match value { StructValue { mut short_name := value.type_name.all_after_last('.') if short_name.contains('__') { short_name = short_name.all_after_last('__') } match short_name { 'Type' { e.sumtype_payload_name(value) } 'Primitive' { e.primitive_type_name(value) } 'Alias' { name_value := value.fields['name'] or { Value('') } name := e.value_string(name_value) if name != '' { name } else if base_type := value.fields['base_type'] { e.types_value_name(base_type) } else { 'alias' } } 'Array' { elem_type := value.fields['elem_type'] or { return '[]void' } '[]' + e.types_value_name(elem_type) } 'ArrayFixed' { len_value := value.fields['len'] or { return '[0]void' } elem_type := value.fields['elem_type'] or { return '[0]void' } '[${e.value_as_int(len_value) or { 0 }}]${e.types_value_name(elem_type)}' } 'Channel' { elem_type_name := e.option_payload_type_name(value.fields['elem_type'] or { return 'chan' }) if elem_type_name == '' { 'chan' } else { 'chan ${elem_type_name}' } } 'Char' { 'char' } 'Enum', 'Interface', 'Struct', 'SumType' { e.value_string(value.fields['name'] or { Value(short_name.to_lower()) }) } 'FnType' { 'fn' } 'ISize' { 'isize' } 'Map' { key_type := value.fields['key_type'] or { return 'map[void]void' } value_type := value.fields['value_type'] or { return 'map[void]void' } 'map[${e.types_value_name(key_type)}]${e.types_value_name(value_type)}' } 'None' { 'none' } 'OptionType' { base_type := value.fields['base_type'] or { return '?void' } '?' + e.types_value_name(base_type) } 'Pointer' { base_type := value.fields['base_type'] or { return '&void' } '&' + e.types_value_name(base_type) } 'ResultType' { base_type := value.fields['base_type'] or { return '!void' } '!' + e.types_value_name(base_type) } 'Rune' { 'rune' } 'String' { 'string' } 'Thread' { elem_type_name := e.option_payload_type_name(value.fields['elem_type'] or { return 'thread' }) if elem_type_name == '' { 'thread' } else { 'thread ${elem_type_name}' } } 'Tuple' { e.tuple_type_name(value) } 'USize' { 'usize' } 'Void' { 'void' } 'Nil' { 'nil' } else { e.value_string(value.fields['name'] or { Value(short_name) }) } } } TypeValue { mut short_name := value.name.all_after_last('.') if short_name.contains('__') { short_name = short_name.all_after_last('__') } short_name } else { e.runtime_type_name(value) } } } fn (mut e Eval) maybe_call_builtin_function(module_name string, fn_name string, args []Value) MaybeValue { if fn_name == 'free' || fn_name.ends_with('__free') { return MaybeValue{ found: true value: void_value() } } if fn_name == 'type_data_ptr_is_nil' || fn_name.ends_with('__type_data_ptr_is_nil') || fn_name == 'type_has_null_data' || fn_name.ends_with('__type_has_null_data') { return MaybeValue{ found: true value: e.type_sum_data_is_nil(safe_arg(args, 0)) } } if module_name == 'types' && (fn_name == 'type_name' || fn_name == 'Type__name' || fn_name.ends_with('Type__name')) { return MaybeValue{ found: true value: e.types_value_name(safe_arg(args, 0)) } } if fn_name == 'map__get_check' || fn_name.ends_with('map__get_check') || (fn_name.starts_with('__Map_') && fn_name.ends_with('_get_check')) { if args.len >= 2 && args[0] is MapValue { map_value := args[0] as MapValue value, found := e.map_lookup(map_value, args[1]) return MaybeValue{ found: true value: if found { value } else { void_value() } } } } if fn_name in ['sync__RwMutex_lock', 'sync__RwMutex_unlock', 'sync__RwMutex_rlock', 'sync__RwMutex_runlock'] || fn_name.ends_with('RwMutex_lock') || fn_name.ends_with('RwMutex_unlock') || fn_name.ends_with('RwMutex_rlock') || fn_name.ends_with('RwMutex_runlock') { return MaybeValue{ found: true value: void_value() } } if module_name == 'token' { token_result := e.maybe_call_token_builtin(fn_name, args) if token_result.found { return token_result } } if module_name == 'ast' { ast_result := e.maybe_call_ast_builtin(fn_name, args) if ast_result.found { return ast_result } } if fn_name.ends_with('__str') && args.len == 1 { return MaybeValue{ found: true value: e.value_string(args[0]) } } if fn_name.ends_with('__eq') && args.len == 2 { return MaybeValue{ found: true value: e.value_eq(args[0], args[1]) } } if fn_name.starts_with('string__') && args.len >= 1 { receiver := e.value_string(args[0]) method_name := fn_name.all_after('string__') if result := e.call_string_method(receiver, method_name, args[1..]) { return MaybeValue{ found: true value: result } } } if array_helper := e.maybe_call_generated_array_helper(fn_name, args) { return array_helper } byte_result := e.maybe_call_byte_builtin(module_name, fn_name, args) if byte_result.found { return byte_result } if fn_name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array', 'new_array_from_c_array_noscan', 'new_array_from_c_array'] && args.len >= 4 { mut values := []Value{} if args[3] is ArrayValue { array_arg := args[3] as ArrayValue values = array_arg.values.clone() } else if args[3] is string { string_arg := args[3] as string for b in string_arg.bytes() { values << i64(b) } } target_len := int(e.value_as_int(args[0]) or { values.len }) if target_len >= 0 && target_len < values.len { values = values[..target_len].clone() } return MaybeValue{ found: true value: ArrayValue{ values: values } } } if fn_name == '__new_array_with_default_noscan' && args.len >= 4 { target_len := int(e.value_as_int(args[0]) or { 0 }) init_value := args[3] mut values := []Value{cap: target_len} for _ in 0 .. target_len { values << init_value } return MaybeValue{ found: true value: ArrayValue{ values: values } } } if module_name in ['', 'builtin'] { match fn_name { 'arguments' { return MaybeValue{ found: true value: ArrayValue{ values: e.program_args().map(Value(it)) } } } 'array__slice' { if args.len < 3 || args[0] !is ArrayValue { return MaybeValue{} } arr := args[0] as ArrayValue start := int(e.value_as_int(args[1]) or { return MaybeValue{} }) end := int(e.value_as_int(args[2]) or { return MaybeValue{} }) return MaybeValue{ found: true value: ArrayValue{ values: arr.values[start..end] } } } 'print' { e.write_stdout(e.value_string(safe_arg(args, 0))) return MaybeValue{ found: true value: void_value() } } 'println' { e.write_stdout(e.value_string(safe_arg(args, 0)) + '\n') return MaybeValue{ found: true value: void_value() } } 'eprint' { e.write_stderr(e.value_string(safe_arg(args, 0))) return MaybeValue{ found: true value: void_value() } } 'eprintln' { e.write_stderr(e.value_string(safe_arg(args, 0)) + '\n') return MaybeValue{ found: true value: void_value() } } 'panic' { panic(e.value_string(safe_arg(args, 0))) } else {} } } if module_name == 'token' && fn_name.ends_with('File__pos') && args.len >= 2 { offset := e.value_as_int(args[1]) or { return MaybeValue{} } mut base := i64(0) mut size := i64(0) if args[0] is StructValue { file := args[0] as StructValue if base_value := file.fields['base'] { base = e.value_as_int(base_value) or { 0 } } if size_value := file.fields['size'] { size = e.value_as_int(size_value) or { 0 } } } if offset > size { return MaybeValue{} } e.next_token_pos_id++ return MaybeValue{ found: true value: StructValue{ type_name: 'token__Pos' fields: { 'offset': Value(base + offset) 'id': Value(e.next_token_pos_id) } } } } if module_name == 'os' { match fn_name { 'execute' { cmd := e.expect_string_arg(args, 0) or { return MaybeValue{} } return MaybeValue{ found: true value: os_result_value(os.execute(cmd)) } } 'execute_opt' { cmd := e.expect_string_arg(args, 0) or { return MaybeValue{} } res := os.execute(cmd) if res.exit_code != 0 { return MaybeValue{ found: true value: wrap_result_err(res.output) } } return MaybeValue{ found: true value: wrap_result_ok(os_result_value(res)) } } 'arguments' { return MaybeValue{ found: true value: ArrayValue{ values: e.program_args().map(Value(it)) } } } 'dir' { return MaybeValue{ found: true value: os.dir(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'file_name' { return MaybeValue{ found: true value: os.file_name(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'join_path' { mut parts := e.array_args_to_strings(args) if parts.len == 0 { return MaybeValue{ found: true value: '' } } mut joined := parts[0] for part in parts[1..] { joined = os.join_path(joined, part) } return MaybeValue{ found: true value: joined } } 'exists' { return MaybeValue{ found: true value: os.exists(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'is_dir' { return MaybeValue{ found: true value: os.is_dir(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'getwd' { return MaybeValue{ found: true value: os.getwd() } } 'temp_dir' { return MaybeValue{ found: true value: os.temp_dir() } } 'getenv' { return MaybeValue{ found: true value: os.getenv(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'getenv_opt' { key := e.expect_string_arg(args, 0) or { return MaybeValue{} } if value := os.getenv_opt(key) { return MaybeValue{ found: true value: wrap_option_ok(value) } } return MaybeValue{ found: true value: wrap_option_none() } } 'is_abs_path' { return MaybeValue{ found: true value: os.is_abs_path(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'norm_path' { return MaybeValue{ found: true value: os.norm_path(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'real_path' { return MaybeValue{ found: true value: os.real_path(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'quoted_path' { return MaybeValue{ found: true value: os.quoted_path(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'base' { return MaybeValue{ found: true value: os.base(e.expect_string_arg(args, 0) or { return MaybeValue{} }) } } 'user_os' { return MaybeValue{ found: true value: os.user_os() } } 'ls' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } if path == '' { return MaybeValue{ found: true value: wrap_result_err('os.ls empty path in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } } items := os.ls(path) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(ArrayValue{ values: items.map(Value(it)) }) } } 'read_file' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } if path == '' { return MaybeValue{ found: true value: wrap_result_err('os.read_file empty path in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } } content := os.read_file(path) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(content) } } 'read_lines' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } lines := os.read_lines(path) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(ArrayValue{ elem_type_name: 'string' values: lines.map(Value(it)) }) } } 'read_bytes' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } bytes := os.read_bytes(path) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(ArrayValue{ elem_type_name: 'u8' values: bytes.map(Value(i64(it))) }) } } 'write_file' { os.write_file(e.expect_string_arg(args, 0) or { return MaybeValue{} }, e.expect_string_arg(args, 1) or { return MaybeValue{} }) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(void_value()) } } 'write_file_array' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } bytes := e.value_to_byte_array(safe_arg(args, 1)) or { return MaybeValue{} } os.write_file_array(path, bytes) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(void_value()) } } 'mkdir_all' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } os.mkdir_all(path) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(void_value()) } } 'rmdir_all' { path := e.expect_string_arg(args, 0) or { return MaybeValue{} } os.rmdir_all(path) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(void_value()) } } 'rm' { os.rm(e.expect_string_arg(args, 0) or { return MaybeValue{} }) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(void_value()) } } 'cp' { os.cp(e.expect_string_arg(args, 0) or { return MaybeValue{} }, e.expect_string_arg(args, 1) or { return MaybeValue{} }) or { return MaybeValue{ found: true value: wrap_result_err(err.msg()) } } return MaybeValue{ found: true value: wrap_result_ok(void_value()) } } else {} } } if module_name == 'time' { match fn_name { 'sys_mono_now', 'sys_mono_now_darwin', 'vpc_now', 'vpc_now_darwin' { return MaybeValue{ found: true value: i64(time.now().unix_micro() * 1000) } } else {} } } if module_name == 'token' { match fn_name { 'File__line_start', 'line_start' { if args.len < 2 || args[0] !is StructValue { return MaybeValue{} } file_value := args[0] as StructValue line := int(e.value_as_int(args[1]) or { return MaybeValue{} }) if line <= 0 { return MaybeValue{ found: true value: i64(0) } } line_offsets := file_value.fields['line_offsets'] or { return MaybeValue{} } if line_offsets !is ArrayValue { return MaybeValue{} } offsets := line_offsets as ArrayValue idx := line - 1 if idx < 0 || idx >= offsets.values.len { return MaybeValue{ found: true value: i64(0) } } return MaybeValue{ found: true value: offsets.values[idx] } } else {} } } if module_name == 'cmdline' { match fn_name { 'option' { if args.len < 1 { return MaybeValue{} } str_arr := e.value_to_string_array(args[0]) or { return MaybeValue{} } opt_arg1 := e.expect_string_arg(args, 1) or { return MaybeValue{} } opt_arg2 := e.expect_string_arg(args, 2) or { return MaybeValue{} } return MaybeValue{ found: true value: host_cmdline.option(str_arr, opt_arg1, opt_arg2) } } 'options' { if args.len < 1 { return MaybeValue{} } str_arr := e.value_to_string_array(args[0]) or { return MaybeValue{} } opt_arg1 := e.expect_string_arg(args, 1) or { return MaybeValue{} } return MaybeValue{ found: true value: ArrayValue{ values: host_cmdline.options(str_arr, opt_arg1).map(Value(it)) } } } 'only_options' { if args.len < 1 { return MaybeValue{} } str_arr := e.value_to_string_array(args[0]) or { return MaybeValue{} } return MaybeValue{ found: true value: ArrayValue{ values: host_cmdline.only_options(str_arr).map(Value(it)) } } } else {} } } return MaybeValue{} } fn (e &Eval) clone_array_item_to_depth(value Value, depth int) Value { if depth < 0 { return value } return match value { ArrayValue { e.clone_array_to_depth(value, depth) } string { value.clone() } else { value } } } fn (e &Eval) clone_array_to_depth(array_value ArrayValue, depth int) ArrayValue { if depth <= 0 { return ArrayValue{ elem_type_name: array_value.elem_type_name values: array_value.values.clone() } } mut values := []Value{cap: array_value.values.len} for item in array_value.values { values << e.clone_array_item_to_depth(item, depth - 1) } return ArrayValue{ elem_type_name: array_value.elem_type_name values: values } } fn (mut e Eval) maybe_call_generated_array_helper(fn_name string, args []Value) ?MaybeValue { if args.len > 0 && args[0] is ArrayValue { array_value := args[0] as ArrayValue if fn_name == 'array__first' || fn_name.ends_with('array__first') { return MaybeValue{ found: true value: e.array_first_value(array_value) or { return none } } } if fn_name == 'array__last' || fn_name.ends_with('array__last') { return MaybeValue{ found: true value: e.array_last_value(array_value) or { return none } } } if fn_name == 'array__clone' || fn_name.ends_with('array__clone') { return MaybeValue{ found: true value: e.clone_array_to_depth(array_value, 0) } } if (fn_name == 'array__clone_to_depth' || fn_name.ends_with('array__clone_to_depth')) && args.len >= 2 { depth := int(e.value_as_int(args[1]) or { return none }) return MaybeValue{ found: true value: e.clone_array_to_depth(array_value, depth) } } } if !fn_name.starts_with('Array_') || args.len == 0 || args[0] !is ArrayValue { return none } array_value := args[0] as ArrayValue if fn_name.ends_with('_first') { return MaybeValue{ found: true value: e.array_first_value(array_value) or { return none } } } if fn_name.ends_with('_last') { return MaybeValue{ found: true value: e.array_last_value(array_value) or { return none } } } if fn_name.ends_with('_contains') && args.len >= 2 { return MaybeValue{ found: true value: e.array_contains(array_value, args[1]) } } if fn_name.ends_with('_has') && args.len >= 2 { return MaybeValue{ found: true value: e.array_has(array_value, args[1]) } } if fn_name.ends_with('_index') && !fn_name.ends_with('_last_index') && args.len >= 2 { return MaybeValue{ found: true value: e.array_index(array_value, args[1], false) } } if fn_name.ends_with('_last_index') && args.len >= 2 { return MaybeValue{ found: true value: e.array_index(array_value, args[1], true) } } if fn_name.ends_with('_join') && args.len >= 2 { return MaybeValue{ found: true value: array_value.values.map(e.value_string(it)).join(e.expect_string_arg(args, 1) or { return none }) } } if fn_name.ends_with('_str') { return MaybeValue{ found: true value: e.value_string(array_value) } } return none } fn (mut e Eval) open_scope() { e.scopes << ScopeFrame{ vars: map[string]Value{} } } fn (mut e Eval) close_scope() ! { if e.scopes.len == 0 { return } scope_idx := e.scopes.len - 1 defers := e.scopes[scope_idx].defers.clone() for i := defers.len - 1; i >= 0; i-- { signal := e.exec_stmts(defers[i])! if signal.kind != .normal { return error('v2.eval: control flow inside defer is not supported') } } e.scopes.pop() } fn (mut e Eval) declare_var(name string, value Value) { if name == '' || name == '_' { return } if e.scopes.len == 0 { e.open_scope() } e.scopes[e.scopes.len - 1].vars[name] = value } fn (mut e Eval) set_var(name string, value Value) ! { for i := e.scopes.len - 1; i >= 0; i-- { if name in e.scopes[i].vars { e.scopes[i].vars[name] = value return } } return e.unknown_variable_error(name) } fn (e &Eval) lookup_var(name string) MaybeValue { for i := e.scopes.len - 1; i >= 0; i-- { if value := e.scopes[i].vars[name] { return MaybeValue{ found: true value: value } } } return MaybeValue{} } fn (mut e Eval) exec_stmts(stmts []ast.Stmt) !FlowSignal { mut label_positions := map[string]int{} for i, stmt in stmts { if stmt is ast.LabelStmt { label_positions[stmt.name] = i } } mut i := 0 for i < stmts.len { stmt := stmts[i] signal := e.exec_stmt(stmt)! if signal.kind == .goto_ { if signal.label in label_positions { i = label_positions[signal.label] continue } return signal } if signal.kind != .normal { return signal } i++ } return FlowSignal{ kind: .normal } } fn (mut e Eval) exec_block(stmts []ast.Stmt) !FlowSignal { e.open_scope() signal := e.exec_stmts(stmts)! e.close_scope()! return signal } fn (mut e Eval) exec_stmt(stmt ast.Stmt) !FlowSignal { match stmt { ast.AssertStmt { passed := e.value_as_bool(e.eval_expr(stmt.expr)!)! if !passed { message := if stmt.extra is ast.EmptyExpr { 'assertion failed' } else { e.value_string(e.eval_expr(stmt.extra)!) } return error('v2.eval: ${message}') } } ast.AssignStmt { return e.exec_assign_stmt(stmt) } ast.BlockStmt { return e.exec_block(stmt.stmts) } ast.ComptimeStmt { return e.exec_stmt(stmt.stmt) } ast.DeferStmt { if e.scopes.len == 0 { return error('v2.eval: defer used outside of a scope') } e.scopes[e.scopes.len - 1].defers << stmt.stmts } ast.ExprStmt { return e.exec_expr_stmt(stmt.expr) } ast.FlowControlStmt { return match stmt.op { .key_break { FlowSignal{ kind: .break_ } } .key_continue { FlowSignal{ kind: .continue_ } } .key_goto { FlowSignal{ kind: .goto_ label: stmt.label } } else { return error('v2.eval: unsupported flow control `${stmt.op}`') } } } ast.ForStmt { return e.exec_for_stmt(stmt) } ast.ReturnStmt { mut values := []Value{cap: stmt.exprs.len} for expr in stmt.exprs { values << e.eval_expr(expr)! } return FlowSignal{ kind: .return_ values: values } } ast.ConstDecl, ast.Directive, ast.EnumDecl, ast.FnDecl, ast.GlobalDecl, ast.ImportStmt, ast.InterfaceDecl, ast.ModuleStmt, ast.StructDecl, ast.TypeDecl, []ast.Attribute { return FlowSignal{ kind: .normal } } ast.LabelStmt { if stmt.stmt is ast.EmptyStmt { return FlowSignal{ kind: .normal } } return e.exec_stmt(stmt.stmt) } else { return error('v2.eval: unsupported statement `${stmt.type_name()}`') } } return FlowSignal{ kind: .normal } } fn (mut e Eval) exec_expr_stmt(expr ast.Expr) !FlowSignal { match expr { ast.ComptimeExpr { if expr.expr is ast.IfExpr { return e.exec_if_stmt(expr.expr) } _ = e.eval_expr(expr.expr)! } ast.IfExpr { return e.exec_if_stmt(expr) } ast.InfixExpr { if expr.op == .left_shift { e.exec_array_append(expr)! } else { _ = e.eval_expr(expr)! } } ast.PostfixExpr { _ = e.eval_postfix_expr(expr)! } ast.UnsafeExpr { return e.exec_block(expr.stmts) } else { _ = e.eval_expr(expr)! } } return FlowSignal{ kind: .normal } } fn (mut e Eval) exec_assign_stmt(stmt ast.AssignStmt) !FlowSignal { mut values := []Value{} for rhs in stmt.rhs { values << e.eval_expr(rhs)! } if stmt.lhs.len > 1 && values.len == 1 && values[0] is TupleValue { tuple_value := values[0] as TupleValue values = tuple_value.values.clone() } if stmt.lhs.len != values.len { return error('v2.eval: assignment arity mismatch') } for i, lhs in stmt.lhs { match stmt.op { .decl_assign { e.assign_target(lhs, values[i], true)! } .assign { e.assign_target(lhs, values[i], false)! } .plus_assign, .minus_assign, .mul_assign, .div_assign, .mod_assign, .and_assign, .or_assign, .xor_assign, .left_shift_assign, .right_shift_assign, .right_shift_unsigned_assign { current := e.eval_expr(lhs)! op := assign_to_infix_token(stmt.op) updated := e.apply_infix(op, current, values[i])! e.assign_target(lhs, updated, false)! } else { return error('v2.eval: unsupported assignment operator `${stmt.op}`') } } } return FlowSignal{ kind: .normal } } fn assign_to_infix_token(op token.Token) token.Token { return match op { .plus_assign { .plus } .minus_assign { .minus } .mul_assign { .mul } .div_assign { .div } .mod_assign { .mod } .and_assign { .amp } .or_assign { .pipe } .xor_assign { .xor } .left_shift_assign { .left_shift } .right_shift_assign { .right_shift } .right_shift_unsigned_assign { .right_shift_unsigned } else { .unknown } } } fn (mut e Eval) assign_target(lhs ast.Expr, value Value, declare bool) ! { if name := assignable_ident_name(lhs) { if name == '_' { return } if declare { e.declare_var(name, value) } else { e.update_target(lhs, value)! } return } if declare { return error('v2.eval: unsupported declaration target `${lhs.type_name()}`') } return e.update_target(lhs, value) } fn can_update_target(expr ast.Expr) bool { if assignable_ident_name(expr) != none || expr is ast.IndexExpr || expr is ast.SelectorExpr { return true } match expr { ast.CastExpr, ast.ModifierExpr, ast.ParenExpr { return can_update_target(expr.expr) } else {} } if expr is ast.PrefixExpr && expr.op in [.mul, .amp] { return can_update_target(expr.expr) } return false } fn (mut e Eval) update_target(expr ast.Expr, value Value) ! { if name := assignable_ident_name(expr) { var_result := e.lookup_var(name) if var_result.found { e.set_var(name, value)! return } if e.set_runtime_const(e.current_module_name(), name, value) { return } return e.unknown_variable_error(name) } if expr is ast.IndexExpr { return e.update_index_target(expr, value) } if expr is ast.SelectorExpr { return e.update_selector_target(expr, value) } match expr { ast.CastExpr, ast.ModifierExpr, ast.ParenExpr { return e.update_target(expr.expr, value) } else {} } if expr is ast.PrefixExpr && expr.op in [.mul, .amp] { return e.update_target(expr.expr, value) } return error('v2.eval: unsupported assignment target `${expr.type_name()}`') } fn assignable_ident_name(expr ast.Expr) ?string { match expr { ast.Ident { return expr.name } ast.ModifierExpr { if expr.expr is ast.Ident { return expr.expr.name } } else {} } return none } fn (mut e Eval) update_index_target(expr ast.IndexExpr, value Value) ! { container := e.eval_expr(expr.lhs)! index_value := e.eval_expr(expr.expr)! match container { ArrayValue { index := int(e.value_as_int(index_value)!) if index < 0 || index >= container.values.len { return error('v2.eval: array index out of bounds') } mut updated := ArrayValue{ ...container values: container.values.clone() } assigned_value := e.adapt_value_to_type_name(value, updated.elem_type_name) updated.values[index] = assigned_value e.update_target(expr.lhs, updated)! return } MapValue { expected_type_name := if container.default_value is VoidValue { '' } else { e.runtime_type_name(container.default_value) } assigned_value := e.adapt_value_to_type_name(value, expected_type_name) e.update_target(expr.lhs, e.map_set_value(container, index_value, assigned_value))! return } else { return error('v2.eval: indexed assignment expects an array or map') } } } fn (mut e Eval) update_selector_target(expr ast.SelectorExpr, value Value) ! { container := e.eval_expr(expr.lhs)! match container { ArrayValue { if expr.rhs.name == 'flags' { return } } StructValue { mut updated := container mut assigned_value := value if field_type := e.lookup_struct_field_type(container.type_name, expr.rhs.name) { assigned_value = e.adapt_value_to_type(value, field_type) } updated.fields[expr.rhs.name] = assigned_value e.update_target(expr.lhs, updated)! return } else { return error('v2.eval: selector assignment expects a struct, got `${e.runtime_type_name(container)}` in `${e.current_function_label()}`') } } } fn (mut e Eval) exec_array_append(expr ast.InfixExpr) ! { container := e.eval_expr(expr.lhs)! value := e.eval_expr(expr.rhs)! match container { ArrayValue { mut updated := container if value is ArrayValue && e.should_append_many(container, value) { if updated.elem_type_name == '' { updated.values << value.values } else { for item in value.values { updated.values << e.adapt_value_to_type_name(item, updated.elem_type_name) } } } else { updated.values << e.adapt_value_to_type_name(value, updated.elem_type_name) } if updated.elem_type_name == '' { updated.elem_type_name = e.infer_array_elem_type(updated.values) } e.update_target(expr.lhs, updated)! return } else { return error('v2.eval: `<<` is only supported for arrays') } } } fn (mut e Eval) exec_for_stmt(stmt ast.ForStmt) !FlowSignal { e.open_scope() defer { e.close_scope() or {} } if stmt.init is ast.ForInStmt { return e.exec_for_in(stmt.init, stmt.stmts) } if stmt.init !is ast.EmptyStmt { init_signal := e.exec_stmt(stmt.init)! if init_signal.kind != .normal { return init_signal } } for { if stmt.cond !is ast.EmptyExpr { if !e.value_as_bool(e.eval_expr(stmt.cond)!)! { break } } body_signal := e.exec_block(stmt.stmts)! if body_signal.kind == .return_ { return body_signal } if body_signal.kind == .break_ { break } if stmt.post !is ast.EmptyStmt { post_signal := e.exec_stmt(stmt.post)! if post_signal.kind == .return_ { return post_signal } if post_signal.kind == .break_ { break } } } return FlowSignal{ kind: .normal } } fn (mut e Eval) exec_for_in(for_in ast.ForInStmt, body []ast.Stmt) !FlowSignal { mut key_name := assignable_ident_name(for_in.key) or { '' } mut value_name := assignable_ident_name(for_in.value) or { '' } if value_name == '' { value_name = key_name key_name = '' } iterable := e.eval_expr(for_in.expr)! match iterable { RangeValue { mut i := iterable.start limit := iterable.end for { if iterable.inclusive { if i > limit { break } } else if i >= limit { break } e.open_scope() if value_name != '' && value_name != '_' { e.declare_var(value_name, i) } body_signal := e.exec_stmts(body)! e.close_scope()! if body_signal.kind == .return_ { return body_signal } if body_signal.kind == .break_ { break } i++ } } ArrayValue { for idx, item in iterable.values { e.open_scope() if key_name != '' && key_name != '_' { e.declare_var(key_name, i64(idx)) } if value_name != '' && value_name != '_' { e.declare_var(value_name, item) } body_signal := e.exec_stmts(body)! e.close_scope()! if body_signal.kind == .return_ { return body_signal } if body_signal.kind == .break_ { break } } } MapValue { for entry in iterable.entries { e.open_scope() if key_name != '' && key_name != '_' { e.declare_var(key_name, entry.key) } if value_name != '' && value_name != '_' { e.declare_var(value_name, entry.value) } body_signal := e.exec_stmts(body)! e.close_scope()! if body_signal.kind == .return_ { return body_signal } if body_signal.kind == .break_ { break } } } string { for idx, ch in iterable.runes() { e.open_scope() if key_name != '' && key_name != '_' { e.declare_var(key_name, i64(idx)) } if value_name != '' && value_name != '_' { e.declare_var(value_name, ch.str()) } body_signal := e.exec_stmts(body)! e.close_scope()! if body_signal.kind == .return_ { return body_signal } if body_signal.kind == .break_ { break } } } else { return error('v2.eval: unsupported for-in iterable `${e.runtime_type_name(iterable)}`') } } return FlowSignal{ kind: .normal } } fn (mut e Eval) exec_if_stmt(expr ast.IfExpr) !FlowSignal { should_run := if expr.cond is ast.EmptyExpr { true } else { e.value_as_bool(e.eval_expr(expr.cond)!)! } if should_run { return e.exec_block(expr.stmts) } if expr.else_expr is ast.IfExpr { return e.exec_if_stmt(expr.else_expr) } if expr.else_expr is ast.EmptyExpr { return FlowSignal{ kind: .normal } } _ = e.eval_expr(expr.else_expr)! return FlowSignal{ kind: .normal } } fn (mut e Eval) eval_expr(expr ast.Expr) !Value { match expr { ast.ArrayInitExpr { mut elem_type_name := if expr.typ is ast.EmptyExpr { '' } else { e.array_elem_type_name(expr.typ) } if expr.len !is ast.EmptyExpr && expr.exprs.len == 0 { size := int(e.value_as_int(e.eval_expr(expr.len)!)!) init_value := if expr.init is ast.EmptyExpr { void_value() } else { e.eval_expr(expr.init)! } mut values := []Value{len: size, init: init_value} if elem_type_name == '' { elem_type_name = e.infer_array_elem_type(values) } return ArrayValue{ elem_type_name: elem_type_name values: values } } mut values := []Value{cap: expr.exprs.len} // Spread syntax `[...base, e1, e2]` — prepend the base array's // values before any explicit elements. Deep-clone each item so // the new array does not alias the base's nested storage (mirrors // the runtime `array__clone_to_depth` semantics used by lowered // codegen paths). if expr.update_expr !is ast.EmptyExpr { base_value := e.eval_expr(expr.update_expr)! if base_value is ArrayValue { cloned_base := e.clone_array_to_depth(base_value, 100) for v in cloned_base.values { values << v } if elem_type_name == '' { elem_type_name = base_value.elem_type_name } } } for item in expr.exprs { values << e.eval_expr(item)! } if elem_type_name == '' { elem_type_name = e.infer_array_elem_type(values) } return ArrayValue{ elem_type_name: elem_type_name values: values } } ast.InitExpr { mut fields := map[string]Value{} mut data_fields := map[string]Value{} type_name := expr.typ.name() for field in expr.fields { mut value := e.eval_expr(field.value)! if field.name != '' && !field.name.starts_with('_data.') { if field_type := e.lookup_struct_field_type(type_name, field.name) { value = e.adapt_value_to_type(value, field_type) } } if field.name.starts_with('_data.') { data_fields[field.name.all_after('_data.')] = value continue } fields[field.name] = value } if data_fields.len > 0 { mut data_struct := StructValue{ type_name: type_name + '._data' fields: map[string]Value{} } if existing_data := fields['_data'] { if existing_data is StructValue { data_struct = existing_data } } mut merged_data_fields := data_struct.fields.clone() for name, value in data_fields { merged_data_fields[name] = value } fields['_data'] = StructValue{ type_name: data_struct.type_name fields: merged_data_fields } } return StructValue{ type_name: type_name fields: fields } } ast.AsCastExpr { return e.eval_as_cast_expr(expr) } ast.BasicLiteral { return e.eval_basic_literal(expr) } ast.CallExpr { return e.eval_call_expr(expr) } ast.CallOrCastExpr { if e.is_type_expr(expr.lhs) { value := e.eval_expr(expr.expr)! target_name := expr.lhs.name() if e.is_sum_type_name(target_name) { return e.wrap_sumtype_value(target_name, value, expr.expr) or { e.cast_value(value, target_name)! } } return e.cast_value(value, target_name) } return e.eval_call_expr(ast.CallExpr{ lhs: expr.lhs args: [expr.expr] pos: expr.pos }) } ast.CastExpr { value := e.eval_expr(expr.expr)! target_name := expr.typ.name() if e.is_sum_type_name(target_name) { return e.wrap_sumtype_value(target_name, value, expr.expr) or { e.cast_value(value, target_name)! } } return e.cast_value(value, target_name) } ast.ComptimeExpr { return e.eval_expr(expr.expr) } ast.GenericArgs { if e.is_type_expr(expr) { return TypeValue{ name: generic_args_name(expr) } } return error('v2.eval: unsupported generic expression `${generic_args_name(expr)}`') } ast.Ident { return e.eval_ident(expr.name) } ast.IfExpr { return e.eval_if_value(expr) } ast.IndexExpr { return e.eval_index_expr(expr) } ast.InfixExpr { return e.eval_infix_expr(expr) } ast.KeywordOperator { return e.eval_keyword_operator(expr) } ast.MapInitExpr { mut entries := []MapEntry{cap: expr.keys.len} for i, key_expr in expr.keys { key := e.eval_expr(key_expr)! val := if i < expr.vals.len { e.eval_expr(expr.vals[i])! } else { void_value() } entries << MapEntry{ key: key value: val } } default_value := if expr.typ is ast.Type && expr.typ is ast.MapType { e.zero_value_for_type_expr((expr.typ as ast.MapType).value_type) } else if entries.len > 0 { e.zero_value_like(entries[0].value) } else { void_value() } return MapValue{ default_value: default_value entries: entries } } ast.ModifierExpr { return e.eval_expr(expr.expr) } ast.ParenExpr { return e.eval_expr(expr.expr) } ast.PostfixExpr { return e.eval_postfix_expr(expr) } ast.PrefixExpr { return e.eval_prefix_expr(expr) } ast.RangeExpr { return RangeValue{ start: e.value_as_int(e.eval_expr(expr.start)!)! end: e.value_as_int(e.eval_expr(expr.end)!)! inclusive: expr.op == .ellipsis } } ast.SelectorExpr { return e.eval_selector_expr(expr) } ast.StringInterLiteral { return e.eval_string_inter(expr) } ast.StringLiteral { return decode_string_literal(expr.value) } ast.Tuple { mut values := []Value{cap: expr.exprs.len} for item in expr.exprs { values << e.eval_expr(item)! } return TupleValue{ values: values } } ast.Type { match expr { ast.NilType, ast.NoneType { return void_value() } else {} } return TypeValue{ name: e.type_node_name(expr) } } ast.UnsafeExpr { return e.eval_unsafe_expr(expr) } ast.EmptyExpr { return void_value() } else { return error('v2.eval: unsupported expression `${expr.type_name()}`') } } } fn (mut e Eval) eval_as_cast_expr(expr ast.AsCastExpr) !Value { value := e.eval_expr(expr.expr)! target_name := e.type_expr_name(expr.typ) if value is StructValue { if payload := e.unwrap_sumtype_value(value, target_name) { return payload } if e.value_matches_type_name(value, target_name) { return value } return error('v2.eval: `${value.type_name}` is not `${target_name}` in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } if e.value_matches_type_name(value, target_name) { return e.cast_value(value, target_name) } return error('v2.eval: `${e.runtime_type_name(value)}` is not `${target_name}` in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } fn (mut e Eval) eval_basic_literal(expr ast.BasicLiteral) !Value { if expr.kind == .key_true { v := Value(true) return v } else if expr.kind == .key_false { v := Value(false) return v } else if expr.kind == .number { if expr.value.contains('.') || expr.value.contains('e') || expr.value.contains('E') { f := strconv.atof64(expr.value)! v := Value(f) return v } else { n := strconv.parse_int(expr.value, 0, 64)! v := Value(n) return v } } else if expr.kind == .char { if expr.value.len == 0 { v := Value(i64(0)) return v } else { decoded := decode_string_literal(expr.value) if decoded.len == 0 { v := Value(i64(0)) return v } v := Value(i64(decoded.runes()[0])) return v } } else { return error('v2.eval: unsupported literal `${expr.kind}`') } } fn (mut e Eval) eval_ident(name string) !Value { value_result := e.lookup_var(name) if value_result.found { return value_result.value } module_name := e.resolve_module_name(name) if module_name != '' { return ModuleValue{ name: module_name } } if name == 'nil' || name == 'none' { return void_value() } if e.is_builtin_type_name(name) { return TypeValue{ name: name } } cur_module := e.current_module_name() if e.has_type_name(cur_module, name) { return TypeValue{ name: '${cur_module}.${name}' } } if type_value := e.resolve_mangled_type_value(name) { return type_value } const_result := e.lookup_const(cur_module, name) if const_result.found { return const_result.value } builtin_const := e.lookup_const('builtin', name) if builtin_const.found { return builtin_const.value } if mangled_const := e.resolve_mangled_const(name) { return mangled_const } return error('v2.eval: unknown identifier `${name}` in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } fn (e &Eval) resolve_module_name(name string) string { file_name := e.current_file_name() if file_name != '' { if aliases := e.file_import_alias[file_name] { if name in aliases { return aliases[name] } } } if name in e.modules { return name } return '' } fn (e &Eval) has_type_name(module_name string, type_name string) bool { if module_name !in e.type_names { return false } return type_name in e.type_names[module_name] } fn (mut e Eval) resolve_mangled_const(name string) ?Value { for module_name in e.consts.keys() { prefix := e.mangled_module_name(module_name) + '__' if !name.starts_with(prefix) { continue } key := name[prefix.len..].all_after_last('__') const_result := e.lookup_const(module_name, key) if const_result.found { return const_result.value } } if name.contains('__') { type_name := name.all_before_last('__') field_name := name.all_after_last('__') for module_name, module_types in e.type_names { if type_name !in module_types { continue } const_result := e.lookup_const(module_name, field_name) if const_result.found { return const_result.value } } } return none } fn (e &Eval) resolve_mangled_type_value(name string) ?TypeValue { for module_name, module_types in e.type_names { prefix := e.mangled_module_name(module_name) + '__' if !name.starts_with(prefix) { continue } type_name := name[prefix.len..] if type_name in module_types { return TypeValue{ name: '${module_name}.${type_name}' } } } return none } fn (e &Eval) mangled_module_name(name string) string { if name == '' { return '' } return name.replace('.', '__') } fn (mut e Eval) lookup_const(module_name string, name string) MaybeValue { if module_name !in e.consts { return MaybeValue{} } mut entry := e.consts[module_name][name] or { return MaybeValue{} } if entry.cached { return MaybeValue{ found: true value: entry.value } } if entry.evaluating { return MaybeValue{} } entry.evaluating = true e.consts[module_name][name] = entry e.call_stack << CallFrame{ module_name: module_name file_name: entry.file_name fn_name: 'const ${name}' } value := e.eval_expr(entry.expr) or { entry.evaluating = false e.consts[module_name][name] = entry e.call_stack.pop() return MaybeValue{} } e.call_stack.pop() entry.cached = true entry.evaluating = false entry.value = value e.consts[module_name][name] = entry return MaybeValue{ found: true value: value } } fn (mut e Eval) set_runtime_const(module_name string, name string, value Value) bool { if module_name !in e.consts { return false } mut entry := e.consts[module_name][name] or { return false } entry.cached = true entry.evaluating = false entry.value = value e.consts[module_name][name] = entry return true } fn (mut e Eval) eval_call_expr(expr ast.CallExpr) !Value { if expr.lhs is ast.SelectorExpr { selector := expr.lhs as ast.SelectorExpr if builder_result := e.maybe_call_builder_wrapper(selector.rhs.name, expr.args) { return builder_result } if selector.lhs is ast.SelectorExpr { inner := selector.lhs as ast.SelectorExpr if inner.rhs.name == 'flags' { _ = e.eval_expr(inner.lhs)! for arg in expr.args { _ = e.eval_expr(arg)! } return match selector.rhs.name { 'set', 'clear' { void_value() } 'has' { Value(false) } else { return error('v2.eval: unsupported array flags method `${selector.rhs.name}`') } } } } if selector.lhs is ast.Ident { module_name := e.resolve_module_name(selector.lhs.name) if module_name != '' { mut args := []Value{cap: expr.args.len} for arg in expr.args { args << e.eval_expr(arg)! } result := e.call_function(module_name, selector.rhs.name, args)! e.writeback_mut_args(expr.args, result)! return call_result_value(result) } } receiver := e.eval_expr(selector.lhs)! mut args := []Value{cap: expr.args.len} for arg in expr.args { args << e.eval_expr(arg)! } if target := e.resolve_method_target(receiver, selector.rhs.name) { mut method_args := []Value{cap: args.len + 1} method_args << receiver method_args << args result := e.call_function(target.module_name, target.fn_name, method_args)! e.writeback_method_mut_args(selector.lhs, expr.args, result)! return call_result_value(result) } return e.call_value_method(receiver, selector.rhs.name, args) } if expr.lhs is ast.Ident { if builder_result := e.maybe_call_builder_wrapper(expr.lhs.name, expr.args) { return builder_result } mut args := []Value{cap: expr.args.len} for arg in expr.args { args << e.eval_expr(arg)! } if target := e.resolve_mangled_function_target(expr.lhs.name) { result := e.call_function(target.module_name, target.fn_name, args)! e.writeback_mut_args(expr.args, result)! return call_result_value(result) } result := e.call_function(e.current_module_name(), expr.lhs.name, args) or { if result := e.call_function('builtin', expr.lhs.name, args) { e.writeback_mut_args(expr.args, result)! return call_result_value(result) } return error(err.msg()) } e.writeback_mut_args(expr.args, result)! return call_result_value(result) } return error('v2.eval: unsupported call target `${expr.lhs.type_name()}`') } fn (mut e Eval) writeback_method_mut_args(receiver_expr ast.Expr, arg_exprs []ast.Expr, result CallResult) ! { for idx, value in result.mut_args { if idx == 0 { if can_update_target(receiver_expr) { e.update_target(receiver_expr, value)! } continue } arg_idx := idx - 1 if arg_idx >= 0 && arg_idx < arg_exprs.len && can_update_target(arg_exprs[arg_idx]) { e.update_target(arg_exprs[arg_idx], value)! } } } fn (mut e Eval) maybe_call_builder_wrapper(name string, args []ast.Expr) ?Value { if !name.contains('Builder__') || args.len == 0 { return none } method_name := name.all_after('Builder__') target_expr := builder_target_expr(args[0]) or { return none } target_value := e.eval_expr(target_expr) or { return none } if target_value !is ArrayValue { return none } mut receiver := target_value as ArrayValue match method_name { 'cut_last' { if args.len < 2 { return none } count_value := e.eval_expr(args[1]) or { return none } n := int(e.value_as_int(count_value) or { return none }) mut items := receiver.values.clone() if n <= 0 || n > items.len { return '' } cut := items[items.len - n..].clone() items = items[..items.len - n].clone() e.update_target(target_expr, ArrayValue{ values: items }) or { return none } return e.builder_array_to_string(ArrayValue{ values: cut }) } 'go_back' { if args.len < 2 { return none } count_value := e.eval_expr(args[1]) or { return none } n := int(e.value_as_int(count_value) or { return none }) if n <= 0 { return void_value() } keep_len := if n > receiver.values.len { 0 } else { receiver.values.len - n } e.update_target(target_expr, ArrayValue{ values: receiver.values[..keep_len].clone() }) or { return none } return void_value() } 'go_back_to' { if args.len < 2 { return none } pos_value := e.eval_expr(args[1]) or { return none } pos := int(e.value_as_int(pos_value) or { return none }) keep_len := if pos < 0 { 0 } else if pos > receiver.values.len { receiver.values.len } else { pos } e.update_target(target_expr, ArrayValue{ values: receiver.values[..keep_len].clone() }) or { return none } return void_value() } 'last_n' { if args.len < 2 { return none } count_value := e.eval_expr(args[1]) or { return none } n := int(e.value_as_int(count_value) or { return none }) return e.builder_tail_string(receiver, n) } 'after' { if args.len < 2 { return none } start_value := e.eval_expr(args[1]) or { return none } start := int(e.value_as_int(start_value) or { return none }) return e.builder_slice_string(receiver, start, receiver.values.len - start) } 'spart' { if args.len < 3 { return none } start_value := e.eval_expr(args[1]) or { return none } len_value := e.eval_expr(args[2]) or { return none } start := int(e.value_as_int(start_value) or { return none }) n := int(e.value_as_int(len_value) or { return none }) return e.builder_slice_string(receiver, start, n) } 'str' { result := e.builder_array_to_string(receiver) e.update_target(target_expr, ArrayValue{ values: []Value{} }) or { return none } return result } 'write_ptr' { return void_value() } 'write_byte', 'write_rune', 'write_repeated_rune', 'write_string', 'write_string2', 'write_u8', 'writeln', 'writeln2' { mut items := receiver.values.clone() match method_name { 'write_string' { if args.len < 2 { return none } value := e.eval_expr(args[1]) or { return none } e.append_builder_string(mut items, e.value_string(value)) } 'write_string2' { if args.len < 3 { return none } first_value := e.eval_expr(args[1]) or { return none } second_value := e.eval_expr(args[2]) or { return none } e.append_builder_string(mut items, e.value_string(first_value)) e.append_builder_string(mut items, e.value_string(second_value)) } 'write_rune' { if args.len < 2 { return none } rune_value := e.eval_expr(args[1]) or { return none } e.append_builder_rune(mut items, e.value_as_int(rune_value) or { return none }) } 'write_repeated_rune' { if args.len < 3 { return none } rune_value := e.eval_expr(args[1]) or { return none } count_value := e.eval_expr(args[2]) or { return none } r := e.value_as_int(rune_value) or { return none } count := int(e.value_as_int(count_value) or { return none }) for _ in 0 .. count { e.append_builder_rune(mut items, r) } } 'write_byte', 'write_u8' { if args.len < 2 { return none } byte_value := e.eval_expr(args[1]) or { return none } items << (e.value_as_int(byte_value) or { return none }) } 'writeln' { if args.len < 2 { return none } value := e.eval_expr(args[1]) or { return none } e.append_builder_string(mut items, e.value_string(value)) items << i64(`\n`) } 'writeln2' { if args.len < 3 { return none } first_value := e.eval_expr(args[1]) or { return none } second_value := e.eval_expr(args[2]) or { return none } e.append_builder_string(mut items, e.value_string(first_value)) items << i64(`\n`) e.append_builder_string(mut items, e.value_string(second_value)) items << i64(`\n`) } else {} } e.update_target(target_expr, ArrayValue{ values: items }) or { return none } return void_value() } else { return none } } } fn builder_target_expr(expr ast.Expr) ?ast.Expr { match expr { ast.CastExpr { return builder_target_expr(expr.expr) } ast.Ident { return expr } ast.ModifierExpr { return builder_target_expr(expr.expr) } ast.ParenExpr { return builder_target_expr(expr.expr) } ast.SelectorExpr { return expr } ast.PrefixExpr { if expr.op == .amp { return builder_target_expr(expr.expr) } } else {} } return none } fn receiver_method_type_candidates(receiver Value) []string { return match receiver { StructValue { struct_field_lookup_candidates(receiver.type_name) } TypeValue { struct_field_lookup_candidates(receiver.name) } else { []string{} } } } fn (e &Eval) resolve_method_target(receiver Value, method_name string) ?MaybeFunctionTarget { candidates := receiver_method_type_candidates(receiver) if candidates.len == 0 { return none } for module_name, functions in e.functions { for candidate in candidates { key := '${candidate}__${method_name}' if key in functions { return MaybeFunctionTarget{ found: true module_name: module_name fn_name: key } } } } return none } fn (e &Eval) append_builder_string(mut items []Value, s string) { for b in s.bytes() { items << i64(b) } } fn (e &Eval) append_builder_rune(mut items []Value, r i64) { e.append_builder_string(mut items, rune(r).str()) } fn (e &Eval) builder_array_to_string(receiver ArrayValue) string { mut bytes := []u8{cap: receiver.values.len} for item in receiver.values { bytes << u8(e.value_as_int(item) or { 0 }) } return bytes.bytestr() } fn (e &Eval) builder_slice_string(receiver ArrayValue, start_pos int, n int) string { if start_pos < 0 || n <= 0 || start_pos >= receiver.values.len { return '' } end := if start_pos + n > receiver.values.len { receiver.values.len } else { start_pos + n } return e.builder_array_to_string(ArrayValue{ values: receiver.values[start_pos..end].clone() }) } fn (e &Eval) builder_tail_string(receiver ArrayValue, n int) string { if n <= 0 || n > receiver.values.len { return '' } return e.builder_array_to_string(ArrayValue{ values: receiver.values[receiver.values.len - n..].clone() }) } fn (mut e Eval) eval_if_value(expr ast.IfExpr) !Value { should_run := if expr.cond is ast.EmptyExpr { true } else { e.value_as_bool(e.eval_expr(expr.cond)!)! } if should_run { return e.eval_block_value(expr.stmts) } if expr.else_expr is ast.IfExpr { return e.eval_if_value(expr.else_expr) } if expr.else_expr is ast.EmptyExpr { return void_value() } return e.eval_expr(expr.else_expr) } fn (e &Eval) resolve_mangled_function_target(name string) ?MaybeFunctionTarget { for module_name, functions in e.functions { prefix := e.mangled_module_name(module_name) + '__' if !name.starts_with(prefix) { continue } fn_name := name[prefix.len..] if fn_name in functions { return MaybeFunctionTarget{ found: true module_name: module_name fn_name: fn_name } } } return none } fn (mut e Eval) eval_block_value(stmts []ast.Stmt) !Value { e.open_scope() defer { e.close_scope() or {} } if stmts.len == 0 { return void_value() } for i, stmt in stmts { is_last := i == stmts.len - 1 if is_last && stmt is ast.ExprStmt { if stmt.expr is ast.IfExpr { return e.eval_if_value(stmt.expr) } return e.eval_expr(stmt.expr) } signal := e.exec_stmt(stmt)! if signal.kind != .normal { return error('v2.eval: control flow inside expression block is not supported yet') } } return void_value() } fn (mut e Eval) eval_index_expr(expr ast.IndexExpr) !Value { container := e.eval_expr(expr.lhs)! if expr.expr is ast.RangeExpr { range_value := e.eval_expr(expr.expr)! as RangeValue start := int(range_value.start) end := int(range_value.end) + if range_value.inclusive { 1 } else { 0 } match container { ArrayValue { return ArrayValue{ values: container.values[start..end] } } string { s := container[start..end] return s } else { return error('v2.eval: slicing is only supported for arrays and strings') } } } index_value := e.eval_expr(expr.expr)! match container { ArrayValue { index := int(e.value_as_int(index_value)!) if index < 0 || index >= container.values.len { return error('v2.eval: array index out of bounds') } return container.values[index] } MapValue { return e.map_get_or_default(container, index_value) } string { index := int(e.value_as_int(index_value)!) if index < 0 || index >= container.len { return error('v2.eval: string index out of bounds') } return i64(container[index]) } else { return error('v2.eval: unsupported index target `${e.runtime_type_name(container)}`') } } } fn (mut e Eval) eval_infix_expr(expr ast.InfixExpr) !Value { if expr.op == .and { left := e.value_as_bool(e.eval_expr(expr.lhs)!)! if !left { return Value(false) } return e.value_as_bool(e.eval_expr(expr.rhs)!)! } if expr.op == .logical_or { left := e.value_as_bool(e.eval_expr(expr.lhs)!)! if left { return Value(true) } return e.value_as_bool(e.eval_expr(expr.rhs)!)! } left := e.eval_expr(expr.lhs)! right := e.eval_expr(expr.rhs)! return e.apply_infix(expr.op, left, right) } fn (mut e Eval) apply_infix(op token.Token, left Value, right Value) !Value { match op { .plus { if left is string || right is string { return e.value_string(left) + e.value_string(right) } if left is f64 || right is f64 { return e.value_as_f64(left)! + e.value_as_f64(right)! } return e.value_as_int(left)! + e.value_as_int(right)! } .minus { if left is f64 || right is f64 { return e.value_as_f64(left)! - e.value_as_f64(right)! } return e.value_as_int(left)! - e.value_as_int(right)! } .mul { if left is f64 || right is f64 { return e.value_as_f64(left)! * e.value_as_f64(right)! } return e.value_as_int(left)! * e.value_as_int(right)! } .div { if left is f64 || right is f64 { return e.value_as_f64(left)! / e.value_as_f64(right)! } return e.value_as_int(left)! / e.value_as_int(right)! } .mod { return e.value_as_int(left)! % e.value_as_int(right)! } .eq { return e.value_eq(left, right) } .ne { return !e.value_eq(left, right) } .lt { return e.compare_values(left, right) < 0 } .le { return e.compare_values(left, right) <= 0 } .gt { return e.compare_values(left, right) > 0 } .ge { return e.compare_values(left, right) >= 0 } .key_is { return e.value_is_type(left, right) } .not_is { return !e.value_is_type(left, right) } .amp { return e.value_as_int(left)! & e.value_as_int(right)! } .pipe { return e.value_as_int(left)! | e.value_as_int(right)! } .xor { return e.value_as_int(left)! ^ e.value_as_int(right)! } .left_shift { return i64(u64(e.value_as_int(left)!) << u64(e.value_as_int(right)!)) } .right_shift { return e.value_as_int(left)! >> e.value_as_int(right)! } .right_shift_unsigned { return i64(u64(e.value_as_int(left)!) >> u64(e.value_as_int(right)!)) } .key_in { match right { ArrayValue { for item in right.values { if e.value_eq(left, item) { return true } } return false } string { return right.contains(e.value_string(left)) } MapValue { return e.map_contains_key(right, left) } else { return error('v2.eval: unsupported `in` rhs `${e.runtime_type_name(right)}`') } } } .not_in { in_result := e.apply_infix(.key_in, left, right)! return !e.value_as_bool(in_result)! } else { return error('v2.eval: unsupported infix operator `${op}`') } } } fn (e &Eval) value_is_type(value Value, rhs Value) bool { target_name := match rhs { TypeValue { rhs.name } string { rhs } else { e.runtime_type_name(rhs) } } if value is StructValue && e.unwrap_sumtype_value(value, target_name) != none { return true } return e.value_matches_type_name(value, target_name) } fn (e &Eval) compare_values(left Value, right Value) int { if left is string || right is string { ls := e.value_string(left) rs := e.value_string(right) if ls < rs { return -1 } if ls > rs { return 1 } return 0 } if left is f64 || right is f64 { lf := e.value_as_f64(left) or { 0.0 } rf := e.value_as_f64(right) or { 0.0 } if lf < rf { return -1 } if lf > rf { return 1 } return 0 } li := e.value_as_int(left) or { 0 } ri := e.value_as_int(right) or { 0 } if li < ri { return -1 } if li > ri { return 1 } return 0 } fn (e &Eval) value_eq(left Value, right Value) bool { match left { bool { return right is bool && left == right } i64 { if right is i64 { return left == right } if right is f64 { return f64(left) == value_f64_payload(right) } return false } f64 { left_f := value_f64_payload(left) if right is f64 { return left_f == value_f64_payload(right) } if right is i64 { return left_f == f64(right) } return false } string { return right is string && left == right } ArrayValue { if right !is ArrayValue { return false } right_array := right as ArrayValue if left.values.len != right_array.values.len { return false } for i, item in left.values { if !e.value_eq(item, right_array.values[i]) { return false } } return true } MapValue { if right !is MapValue { return false } right_map := right as MapValue if left.entries.len != right_map.entries.len { return false } for entry in left.entries { right_value, found := e.map_lookup(right_map, entry.key) if !found || !e.value_eq(entry.value, right_value) { return false } } return true } StructValue { if right !is StructValue { return false } right_struct := right as StructValue if left.type_name != right_struct.type_name || left.fields.len != right_struct.fields.len { return false } for key, value in left.fields { right_value := right_struct.fields[key] or { return false } if !e.value_eq(value, right_value) { return false } } return true } FlagsValue { return right is FlagsValue } ModuleValue { return right is ModuleValue && left.name == right.name } TypeValue { return right is TypeValue && left.name == right.name } VoidValue { return right is VoidValue } TupleValue { if right !is TupleValue { return false } right_tuple := right as TupleValue if left.values.len != right_tuple.values.len { return false } for i, item in left.values { if !e.value_eq(item, right_tuple.values[i]) { return false } } return true } RangeValue { return right is RangeValue && left.start == right.start && left.end == right.end && left.inclusive == right.inclusive } } } fn (mut e Eval) eval_keyword_operator(expr ast.KeywordOperator) !Value { match expr.op { .key_isreftype { if expr.exprs.len == 0 { return error('v2.eval: isreftype expects one argument') } if e.is_type_expr(expr.exprs[0]) { return e.is_ref_type_name(e.type_expr_name(expr.exprs[0])) } value := e.eval_expr(expr.exprs[0])! return e.is_ref_type_name(e.runtime_type_name(value)) } .key_sizeof { if expr.exprs.len == 0 { return error('v2.eval: sizeof expects one argument') } if e.is_type_expr(expr.exprs[0]) { return e.sizeof_type_name(e.type_expr_name(expr.exprs[0])) } value := e.eval_expr(expr.exprs[0])! return e.sizeof_type_name(e.runtime_type_name(value)) } .key_typeof { if expr.exprs.len == 0 { return error('v2.eval: typeof expects one argument') } value := e.eval_expr(expr.exprs[0])! return TypeValue{ name: e.runtime_type_name(value) } } else { return error('v2.eval: unsupported keyword operator `${expr.op}`') } } } fn (mut e Eval) eval_postfix_expr(expr ast.PostfixExpr) !Value { if !can_update_target(expr.expr) { return error('v2.eval: postfix update expects an assignable target') } current := e.eval_expr(expr.expr)! current_i64 := e.value_as_int(current)! updated := if expr.op == .inc { current_i64 + 1 } else { current_i64 - 1 } e.update_target(expr.expr, updated)! return updated } fn (mut e Eval) eval_prefix_expr(expr ast.PrefixExpr) !Value { value := e.eval_expr(expr.expr)! match expr.op { .not { return !e.value_as_bool(value)! } .amp, .mul { return value } .bit_not { return ~e.value_as_int(value)! } .minus { if value is f64 { f := -value_f64_payload(value) return f } return -e.value_as_int(value)! } .plus { return value } else { return error('v2.eval: unsupported prefix operator `${expr.op}`') } } } fn (mut e Eval) eval_selector_expr(expr ast.SelectorExpr) !Value { if expr.lhs is ast.EmptyExpr { const_result := e.lookup_const(e.current_module_name(), expr.rhs.name) if const_result.found { return const_result.value } return error('v2.eval: enum shorthand `${expr.rhs.name}` is not supported yet') } left := e.eval_expr(expr.lhs)! if expr.rhs.name == '_tag' { if tag := e.contextual_sumtype_tag(expr.lhs, left) { return i64(tag) } } if expr.rhs.name == '_data' { if left !is StructValue || '_data' !in (left as StructValue).fields { if _ := e.contextual_sumtype_tag(expr.lhs, left) { return left } } } match left { ModuleValue { prop := e.module_property(left.name, expr.rhs.name) if prop.found { return prop.value } if e.has_type_name(left.name, expr.rhs.name) { return TypeValue{ name: '${left.name}.${expr.rhs.name}' } } if expr.rhs.name.contains('__') { short_name := expr.rhs.name.all_after_last('__') if short_name != '' { if e.has_type_name(left.name, short_name) { return TypeValue{ name: '${left.name}.${short_name}' } } const_result := e.lookup_const(left.name, short_name) if const_result.found { return const_result.value } } module_prefix := e.mangled_module_name(left.name) + '__' if expr.rhs.name.starts_with(module_prefix) { normalized_name := expr.rhs.name[module_prefix.len..] if e.has_type_name(left.name, normalized_name) { return TypeValue{ name: '${left.name}.${normalized_name}' } } } } const_result := e.lookup_const(left.name, expr.rhs.name) if const_result.found { return const_result.value } return error('v2.eval: unknown module selector `${left.name}.${expr.rhs.name}`') } ArrayValue { if expr.rhs.name == 'len' { return i64(left.values.len) } if expr.rhs.name == 'flags' { return FlagsValue{} } if expr.rhs.name == 'str' { return e.value_string(left) } } MapValue { if expr.rhs.name == 'len' { return i64(left.entries.len) } if expr.rhs.name == 'str' { return e.value_string(left) } } StructValue { if expr.rhs.name == 'mtx' { return StructValue{ type_name: 'sync__RwMutex' fields: map[string]Value{} } } if expr.rhs.name == '_tag' { if tag := e.inferred_sumtype_tag(left) { return i64(tag) } } if expr.rhs.name == '_data' && '_data' !in left.fields { if _ := e.inferred_sumtype_tag(left) { return left } } if variant_value := e.sumtype_variant_field_value(left, expr.rhs.name) { return variant_value } if expr.rhs.name in left.fields { field_value := left.fields[expr.rhs.name] or { return error('v2.eval: unknown struct field `${expr.rhs.name}` on `${left.type_name}` fields `${left.fields.keys()}` in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } if left.type_name.ends_with('._data') && expr.rhs.name.starts_with('_') && is_semantically_empty_value(field_value) { if zero_payload := e.zero_value_for_sumtype_data_field(left.type_name, expr.rhs.name) { return zero_payload } } return field_value } if zero_payload := e.zero_value_for_sumtype_data_field(left.type_name, expr.rhs.name) { return zero_payload } if embedded_value := e.lookup_embedded_struct_field_value(left, expr.rhs.name, 0) { return embedded_value } if field_type := e.lookup_struct_field_type(left.type_name, expr.rhs.name) { return e.zero_value_for_type_expr(field_type) } return error('v2.eval: unknown struct field `${expr.rhs.name}` on `${left.type_name}` fields `${left.fields.keys()}` in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } string { if expr.rhs.name == 'len' { return i64(left.len) } if expr.rhs.name == 'str' { return left } } i64 { if expr.rhs.name == 'str' { return e.value_string(left) } if expr.rhs.name == '_tag' || expr.rhs.name == '_data' { return Value(left) } } f64 { if expr.rhs.name == 'str' { return e.value_string(left) } } bool { if expr.rhs.name == 'str' { return e.value_string(left) } } TypeValue { if expr.rhs.name == '_tag' { if tag := e.inferred_sumtype_tag(left) { return i64(tag) } } if expr.rhs.name == '_data' { if _ := e.inferred_sumtype_tag(left) { return left } } module_name := e.type_value_module_name(left) if module_name != '' { const_result := e.lookup_const(module_name, expr.rhs.name) if const_result.found { return const_result.value } } if expr.rhs.name == 'name' { s := left.name v := Value(s) return v } } else {} } return error('v2.eval: unsupported selector `${expr.rhs.name}` on `${e.runtime_type_name(left)}` from `${expr.lhs.type_name()}` `${expr.lhs.name()}` in `${e.current_function_label()}`') } fn (e &Eval) type_value_module_name(value TypeValue) string { if !value.name.contains('.') { return '' } return value.name.all_before_last('.') } fn struct_field_lookup_candidates(type_name string) []string { mut candidates := []string{} mut trimmed := type_name.trim_left('&').trim_right('*') if trimmed == '' { return candidates } add_type_name_alias(mut candidates, trimmed) if trimmed.contains('__') { add_type_name_alias(mut candidates, trimmed.replace('__', '.')) add_type_name_alias(mut candidates, trimmed.all_after_last('__')) } if trimmed.contains('.') { add_type_name_alias(mut candidates, trimmed.all_after_last('.')) } return candidates } fn (e &Eval) qualify_type_name(module_name string, type_name string) string { if module_name == '' || type_name == '' { return type_name } if type_name.starts_with('[]') { return '[]' + e.qualify_type_name(module_name, type_name[2..]) } if type_name.starts_with('?') || type_name.starts_with('!') { return type_name[..1] + e.qualify_type_name(module_name, type_name[1..]) } if type_name.starts_with('&') { return '&' + e.qualify_type_name(module_name, type_name[1..]) } if type_name.starts_with('chan ') { return 'chan ' + e.qualify_type_name(module_name, type_name[5..]) } if type_name.starts_with('thread ') { return 'thread ' + e.qualify_type_name(module_name, type_name[7..]) } if type_name.starts_with('map[') { inner := type_name[4..] if bracket_idx := inner.index(']') { key_name := inner[..bracket_idx] value_name := inner[bracket_idx + 1..] return 'map[${e.qualify_type_name(module_name, key_name)}]${e.qualify_type_name(module_name, value_name)}' } } if (type_name.starts_with('[') && type_name.contains(']')) || type_name.contains('.') || type_name.contains('__') || e.is_builtin_type_name(type_name) || type_name in ['nil', 'none'] { return type_name } if e.has_type_name(module_name, type_name) { return '${module_name}.${type_name}' } return type_name } fn (e &Eval) qualify_type_expr(module_name string, expr ast.Expr) ast.Expr { return match expr { ast.GenericArgs { ast.Expr(ast.GenericArgs{ lhs: e.qualify_type_expr(module_name, expr.lhs) args: expr.args.map(e.qualify_type_expr(module_name, it)) pos: expr.pos }) } ast.Ident { qualified_name := e.qualify_type_name(module_name, expr.name) if qualified_name == expr.name { ast.Expr(expr) } else { parts := qualified_name.split('.') if parts.len == 2 { ast.Expr(ast.SelectorExpr{ lhs: ast.Expr(ast.Ident{ name: parts[0] }) rhs: ast.Ident{ name: parts[1] } }) } else { ast.Expr(expr) } } } ast.ModifierExpr { ast.Expr(ast.ModifierExpr{ kind: expr.kind expr: e.qualify_type_expr(module_name, expr.expr) }) } ast.ParenExpr { ast.Expr(ast.ParenExpr{ expr: e.qualify_type_expr(module_name, expr.expr) }) } ast.Type { match expr { ast.ArrayFixedType { ast.Expr(ast.Type(ast.ArrayFixedType{ len: expr.len elem_type: e.qualify_type_expr(module_name, expr.elem_type) })) } ast.ArrayType { ast.Expr(ast.Type(ast.ArrayType{ elem_type: e.qualify_type_expr(module_name, expr.elem_type) })) } ast.ChannelType { ast.Expr(ast.Type(ast.ChannelType{ cap: expr.cap elem_type: e.qualify_type_expr(module_name, expr.elem_type) })) } ast.GenericType { ast.Expr(ast.Type(ast.GenericType{ name: e.qualify_type_expr(module_name, expr.name) params: expr.params.map(e.qualify_type_expr(module_name, it)) })) } ast.MapType { ast.Expr(ast.Type(ast.MapType{ key_type: e.qualify_type_expr(module_name, expr.key_type) value_type: e.qualify_type_expr(module_name, expr.value_type) })) } ast.OptionType { ast.Expr(ast.Type(ast.OptionType{ base_type: e.qualify_type_expr(module_name, expr.base_type) })) } ast.ResultType { ast.Expr(ast.Type(ast.ResultType{ base_type: e.qualify_type_expr(module_name, expr.base_type) })) } ast.ThreadType { ast.Expr(ast.Type(ast.ThreadType{ elem_type: e.qualify_type_expr(module_name, expr.elem_type) })) } ast.TupleType { ast.Expr(ast.Type(ast.TupleType{ types: expr.types.map(e.qualify_type_expr(module_name, it)) })) } else { expr } } } else { expr } } } fn (e &Eval) lookup_struct_embed_types(type_name string) []string { for candidate in struct_field_lookup_candidates(type_name) { mut module_name := '' mut short_type_name := candidate if candidate.contains('.') { module_name = candidate.all_before_last('.') short_type_name = candidate.all_after_last('.') } if module_name != '' { if module_embeds := e.struct_embeds[module_name] { if embed_names := module_embeds[short_type_name] { return embed_names.map(e.qualify_type_name(module_name, it)) } } continue } current_module := e.current_module_name() if module_embeds := e.struct_embeds[current_module] { if embed_names := module_embeds[short_type_name] { return embed_names.map(e.qualify_type_name(current_module, it)) } } for mod_name, module_embeds in e.struct_embeds { if embed_names := module_embeds[short_type_name] { return embed_names.map(e.qualify_type_name(mod_name, it)) } } } return []string{} } fn (e &Eval) lookup_struct_field_type(type_name string, field_name string) ?ast.Expr { return e.lookup_struct_field_type_with_depth(type_name, field_name, 0) } fn (e &Eval) lookup_struct_field_type_with_depth(type_name string, field_name string, depth int) ?ast.Expr { if depth > 16 { return none } for candidate in struct_field_lookup_candidates(type_name) { mut module_name := '' mut short_type_name := candidate if candidate.contains('.') { module_name = candidate.all_before_last('.') short_type_name = candidate.all_after_last('.') } if module_name != '' { if module_fields := e.struct_field_types[module_name] { if type_fields := module_fields[short_type_name] { if field_name in type_fields { return e.qualify_type_expr(module_name, type_fields[field_name] or { return none }) } } } for embedded_type in e.lookup_struct_embed_types(candidate) { if embedded_field_type := e.lookup_struct_field_type_with_depth(embedded_type, field_name, depth + 1) { return embedded_field_type } } continue } current_module := e.current_module_name() if module_fields := e.struct_field_types[current_module] { if type_fields := module_fields[short_type_name] { if field_name in type_fields { return e.qualify_type_expr(current_module, type_fields[field_name] or { return none }) } } } for mod_name, module_fields in e.struct_field_types { if type_fields := module_fields[short_type_name] { if field_name in type_fields { return e.qualify_type_expr(mod_name, type_fields[field_name] or { return none }) } } } for embedded_type in e.lookup_struct_embed_types(candidate) { if embedded_field_type := e.lookup_struct_field_type_with_depth(embedded_type, field_name, depth + 1) { return embedded_field_type } } } return none } fn (e &Eval) lookup_embedded_struct_field_value(value StructValue, field_name string, depth int) ?Value { if depth > 16 { return none } for embedded_type in e.lookup_struct_embed_types(value.type_name) { for embedded_alias in struct_field_lookup_candidates(embedded_type) { if embedded_value := value.fields[embedded_alias] { if embedded_value is StructValue { embedded_struct := embedded_value as StructValue if field_name in embedded_struct.fields { return embedded_struct.fields[field_name] or { return none } } if nested_value := e.lookup_embedded_struct_field_value(embedded_struct, field_name, depth + 1) { return nested_value } } } } } return none } fn (e &Eval) zero_struct_value(type_name string) StructValue { if info := e.sum_type_info(type_name) { return e.build_sumtype_wrapper(type_name, info, 0, void_value()) } mut fields := map[string]Value{} mut module_name := '' mut short_type_name := type_name if type_name.contains('.') { module_name = type_name.all_before_last('.') short_type_name = type_name.all_after_last('.') } if module_name != '' { if module_fields := e.struct_field_types[module_name] { if type_fields := module_fields[short_type_name] { for field_name, field_type in type_fields { fields[field_name] = e.zero_value_for_type_expr(e.qualify_type_expr(module_name, field_type)) } } } } else if module_fields := e.struct_field_types[e.current_module_name()] { if type_fields := module_fields[short_type_name] { for field_name, field_type in type_fields { fields[field_name] = e.zero_value_for_type_expr(e.qualify_type_expr(e.current_module_name(), field_type)) } } } return StructValue{ type_name: type_name fields: fields } } fn (mut e Eval) eval_string_inter(expr ast.StringInterLiteral) !Value { mut out := '' for i, part in expr.values { mut decoded := decode_string_literal(part) if part in ['"', "'"] && (i == 0 || i == expr.values.len - 1) { decoded = '' } out += decoded if i < expr.inters.len { inter := expr.inters[i] out += e.value_string(e.eval_expr(inter.expr)!) } } return out } fn decode_string_literal(raw string) string { mut s := raw if s.len >= 2 && ((s[0] == `"` && s[s.len - 1] == `"`) || (s[0] == `'` && s[s.len - 1] == `'`)) { s = s[1..s.len - 1] } return s.replace('\\n', '\n').replace('\\r', '\r').replace('\\t', '\t').replace('\\0', '\0').replace('\\"', '"').replace("\\'", "'").replace('\\`', '`').replace('\\\\', '\\').replace('\\$', '$') } fn (mut e Eval) eval_unsafe_expr(expr ast.UnsafeExpr) !Value { if expr.stmts.len == 0 { return void_value() } e.open_scope() defer { e.close_scope() or {} } for i, stmt in expr.stmts { is_last := i == expr.stmts.len - 1 if is_last && stmt is ast.ExprStmt { return e.eval_expr(stmt.expr) } signal := e.exec_stmt(stmt)! if signal.kind != .normal { return error('v2.eval: control flow inside unsafe expression is not supported') } } return void_value() } fn (mut e Eval) call_value_method(receiver Value, method_name string, args []Value) !Value { if method_name == 'str' && args.len == 0 { return e.value_string(receiver) } match receiver { string { return e.call_string_method(receiver, method_name, args) } ArrayValue { return e.call_array_method(receiver, method_name, args) } FlagsValue { return e.call_flags_method(method_name, args) } MapValue { if method_name == 'str' && args.len == 0 { return e.value_string(receiver) } } i64 { if args.len == 0 && receiver >= 0 && receiver <= 255 { if byte_value := eval_byte_method_value(u8(receiver), method_name) { return byte_value } } if args.len == 0 && method_name != 'str' { // Token enum values are stored as ints in eval, so enum helpers need a narrow cast here. if token_value := eval_token_method_value(unsafe { token.Token(int(receiver)) }, method_name) { return token_value } } if method_name == 'str' && args.len == 0 { return e.value_string(receiver) } } f64, bool { if method_name == 'str' && args.len == 0 { return e.value_string(receiver) } } else {} } return error('v2.eval: unsupported method `${method_name}` on `${e.runtime_type_name(receiver)}`') } fn (mut e Eval) call_string_method(receiver string, method_name string, args []Value) !Value { match method_name { 'all_after' { return receiver.all_after(e.expect_string_arg(args, 0)!) } 'all_after_last' { return receiver.all_after_last(e.expect_string_arg(args, 0)!) } 'all_before' { return receiver.all_before(e.expect_string_arg(args, 0)!) } 'all_before_last' { return receiver.all_before_last(e.expect_string_arg(args, 0)!) } 'contains' { return receiver.contains(e.expect_string_arg(args, 0)!) } 'bytes', 'vbytes' { mut values := []Value{cap: receiver.len} for b in receiver.bytes() { values << i64(b) } return ArrayValue{ values: values } } 'clone' { return receiver.clone() } 'ends_with' { return receiver.ends_with(e.expect_string_arg(args, 0)!) } 'ge' { return receiver >= e.expect_string_arg(args, 0)! } 'gt' { return receiver > e.expect_string_arg(args, 0)! } 'int' { return i64(receiver.int()) } 'index' { idx := receiver.index(e.expect_string_arg(args, 0)!) or { return wrap_option_none() } return wrap_option_ok(i64(idx)) } 'i64' { return receiver.i64() } 'last_index' { idx := receiver.last_index(e.expect_string_arg(args, 0)!) or { return wrap_option_none() } return wrap_option_ok(i64(idx)) } 'le' { return receiver <= e.expect_string_arg(args, 0)! } 'lt' { return receiver < e.expect_string_arg(args, 0)! } 'ne' { return receiver != e.expect_string_arg(args, 0)! } 'plus' { return receiver + e.expect_string_arg(args, 0)! } 'plus_two' { return receiver + e.expect_string_arg(args, 0)! + e.expect_string_arg(args, 1)! } 'repeat' { return receiver.repeat(int(e.value_as_int(args[0])!)) } 'replace' { return receiver.replace(e.expect_string_arg(args, 0)!, e.expect_string_arg(args, 1)!) } 'split' { return ArrayValue{ values: receiver.split(e.expect_string_arg(args, 0)!).map(Value(it)) } } 'starts_with' { return receiver.starts_with(e.expect_string_arg(args, 0)!) } 'substr', 'substr_unsafe' { start := int(e.value_as_int(args[0])!) end := int(e.value_as_int(args[1])!) if start < 0 || end < start || end > receiver.len { return '' } substr := receiver[start..end] return substr } 'substr_with_check' { start := int(e.value_as_int(args[0])!) end := int(e.value_as_int(args[1])!) if start < 0 || end < start || end > receiver.len { return wrap_result_err('substring out of bounds') } substr := receiver[start..end] return wrap_result_ok(substr) } 'to_lower' { return receiver.to_lower() } 'to_upper' { return receiver.to_upper() } 'trim' { return receiver.trim(e.expect_string_arg(args, 0)!) } 'trim_right' { return receiver.trim_right(e.expect_string_arg(args, 0)!) } 'trim_space' { return receiver.trim_space() } else { return error('v2.eval: unsupported string method `${method_name}`') } } } fn (e &Eval) call_flags_method(method_name string, args []Value) !Value { _ = args return match method_name { 'set', 'clear' { void_value() } 'has' { Value(false) } else { error('v2.eval: unsupported flags method `${method_name}`') } } } fn (mut e Eval) call_array_method(receiver ArrayValue, method_name string, args []Value) !Value { match method_name { 'has' { return e.array_has(receiver, safe_arg(args, 0)) } 'clone' { return ArrayValue{ elem_type_name: receiver.elem_type_name values: receiver.values.clone() } } 'contains' { return e.array_contains(receiver, safe_arg(args, 0)) } 'first' { return e.array_first_value(receiver) } 'index' { return e.array_index(receiver, safe_arg(args, 0), false) } 'join' { sep := e.expect_string_arg(args, 0)! return receiver.values.map(e.value_string(it)).join(sep) } 'last' { return e.array_last_value(receiver) } 'last_index' { return e.array_index(receiver, safe_arg(args, 0), true) } 'str' { return e.value_string(receiver) } else { return error('v2.eval: unsupported array method `${method_name}`') } } } fn (e &Eval) map_lookup(receiver MapValue, key Value) (Value, bool) { for entry in receiver.entries { if e.value_eq(entry.key, key) { return entry.value, true } } return receiver.default_value, false } fn (e &Eval) map_get_or_default(receiver MapValue, key Value) Value { value, found := e.map_lookup(receiver, key) if found { return value } return receiver.default_value } fn (e &Eval) map_contains_key(receiver MapValue, key Value) bool { _, found := e.map_lookup(receiver, key) return found } fn (e &Eval) map_set_value(receiver MapValue, key Value, value Value) MapValue { default_value := if receiver.default_value is VoidValue { e.zero_value_like(value) } else { receiver.default_value } mut entries := receiver.entries.clone() for i, entry in entries { if e.value_eq(entry.key, key) { entries[i] = MapEntry{ key: key value: value } return MapValue{ default_value: default_value entries: entries } } } entries << MapEntry{ key: key value: value } return MapValue{ default_value: default_value entries: entries } } fn (e &Eval) map_delete_value(receiver MapValue, key Value) MapValue { mut entries := []MapEntry{cap: receiver.entries.len} for entry in receiver.entries { if e.value_eq(entry.key, key) { continue } entries << entry } return MapValue{ default_value: receiver.default_value entries: entries } } fn (e &Eval) map_keys(receiver MapValue) ArrayValue { mut keys := []Value{cap: receiver.entries.len} for entry in receiver.entries { keys << entry.key } return ArrayValue{ elem_type_name: e.infer_array_elem_type(keys) values: keys } } fn (e &Eval) map_values(receiver MapValue) ArrayValue { mut values := []Value{cap: receiver.entries.len} for entry in receiver.entries { values << entry.value } elem_type_name := if receiver.default_value is VoidValue { e.infer_array_elem_type(values) } else { e.runtime_type_name(receiver.default_value) } return ArrayValue{ elem_type_name: elem_type_name values: values } } fn (e &Eval) map_clone(receiver MapValue) MapValue { return MapValue{ default_value: receiver.default_value entries: receiver.entries.clone() } } fn (e &Eval) map_clear(receiver MapValue) MapValue { return MapValue{ default_value: receiver.default_value entries: []MapEntry{} } } fn (e &Eval) zero_value_like(value Value) Value { return match value { ArrayValue { ArrayValue{} } FlagsValue { FlagsValue{} } MapValue { MapValue{ default_value: e.zero_value_like(value.default_value) } } StructValue { e.zero_struct_value(value.type_name) } TupleValue { TupleValue{} } VoidValue { void_value() } bool { false } f64 { f64(0.0) } i64 { i64(0) } string { '' } else { void_value() } } } fn (e &Eval) zero_value_for_type_expr(expr ast.Expr) Value { match expr { ast.GenericArgs { if expr.lhs.name() == 'map' && expr.args.len == 2 { return MapValue{ default_value: e.zero_value_for_type_expr(expr.args[1]) } } return StructValue{ type_name: generic_args_name(expr) fields: map[string]Value{} } } ast.Ident { if expr.name == 'bool' { return false } if expr.name in ['byte', 'char', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'u8', 'u16', 'u32', 'u64', 'usize'] { return i64(0) } if expr.name in ['f32', 'f64'] { return f64(0.0) } if expr.name == 'string' { return '' } type_name := if e.has_type_name(e.current_module_name(), expr.name) { '${e.current_module_name()}.${expr.name}' } else { expr.name } return e.zero_struct_value(type_name) } ast.SelectorExpr { return e.zero_struct_value(expr.name()) } ast.Type { return match expr { ast.ArrayFixedType, ast.ArrayType { ArrayValue{} } ast.MapType { MapValue{ default_value: e.zero_value_for_type_expr(expr.value_type) } } ast.OptionType, ast.ResultType, ast.NilType, ast.NoneType { void_value() } ast.TupleType { TupleValue{ values: expr.types.map(e.zero_value_for_type_expr(it)) } } else { e.zero_struct_value(e.type_node_name(expr)) } } } else { return void_value() } } } fn (e &Eval) array_contains(receiver ArrayValue, needle Value) bool { for item in receiver.values { if e.value_eq(item, needle) { return true } } return false } fn (e &Eval) array_has(receiver ArrayValue, needle Value) bool { if e.array_contains(receiver, needle) { return true } if needle is string { for item in receiver.values { if item is StructValue { if name_value := item.fields['name'] { if e.value_string(name_value) == needle { return true } } } } } return false } fn (e &Eval) array_index(receiver ArrayValue, needle Value, reverse bool) i64 { if reverse { for i := receiver.values.len - 1; i >= 0; i-- { if e.value_eq(receiver.values[i], needle) { return i64(i) } } return -1 } for i, item in receiver.values { if e.value_eq(item, needle) { return i64(i) } } return -1 } fn (e &Eval) array_first_value(receiver ArrayValue) !Value { if receiver.values.len == 0 { return error('v2.eval: array.first on empty array') } return receiver.values[0] } fn (e &Eval) array_last_value(receiver ArrayValue) !Value { if receiver.values.len == 0 { return error('v2.eval: array.last on empty array') } return receiver.values[receiver.values.len - 1] } fn (e &Eval) module_property(module_name string, field_name string) MaybeValue { if module_name == 'os' && field_name == 'args' { return MaybeValue{ found: true value: ArrayValue{ values: e.program_args().map(Value(it)) } } } return MaybeValue{} } fn (e &Eval) program_args() []string { if e.prefs.eval_runtime_args.len > 0 { return e.prefs.eval_runtime_args } return os.args } fn (e &Eval) is_type_expr(expr ast.Expr) bool { match expr { ast.Ident { return e.is_builtin_type_name(expr.name) || e.has_type_name(e.current_module_name(), expr.name) || e.resolve_mangled_type_value(expr.name) != none || e.is_sum_type_name(expr.name) } ast.Type { return true } ast.GenericArgs { return true } else { return false } } } fn generic_args_name(expr ast.GenericArgs) string { mut parts := []string{cap: expr.args.len} for arg in expr.args { parts << arg.name() } return '${expr.lhs.name()}[${parts.join(',')}]' } fn (e &Eval) type_expr_name(expr ast.Expr) string { return match expr { ast.GenericArgs { generic_args_name(expr) } ast.Ident { expr.name } ast.ModifierExpr { e.type_expr_name(expr.expr) } ast.ParenExpr { e.type_expr_name(expr.expr) } ast.SelectorExpr { expr.name() } ast.Type { e.type_node_name(expr) } else { expr.name() } } } fn (e &Eval) type_node_name(typ ast.Type) string { return match typ { ast.AnonStructType { 'struct' } ast.ArrayFixedType { '[${typ.len.name()}]${e.type_expr_name(typ.elem_type)}' } ast.ArrayType { '[]${e.type_expr_name(typ.elem_type)}' } ast.ChannelType { 'chan ${e.type_expr_name(typ.elem_type)}' } ast.FnType { 'fn' } ast.GenericType { '${typ.name.name()}[${typ.params.name_list()}]' } ast.MapType { 'map[${e.type_expr_name(typ.key_type)}]${e.type_expr_name(typ.value_type)}' } ast.NilType { 'nil' } ast.NoneType { 'none' } ast.OptionType { '?${e.type_expr_name(typ.base_type)}' } ast.PointerType { if typ.lifetime != '' { '&^${typ.lifetime} ${e.type_expr_name(typ.base_type)}' } else { '&${e.type_expr_name(typ.base_type)}' } } ast.ResultType { '!${e.type_expr_name(typ.base_type)}' } ast.ThreadType { if typ.elem_type is ast.EmptyExpr { 'thread' } else { 'thread ${e.type_expr_name(typ.elem_type)}' } } ast.TupleType { '(' + typ.types.map(e.type_expr_name(it)).join(',') + ')' } } } fn (e &Eval) is_builtin_type_name(name string) bool { return name in ['bool', 'byte', 'char', 'f32', 'f64', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'string', 'u8', 'u16', 'u32', 'u64', 'usize', 'voidptr', 'byteptr'] } fn (e &Eval) is_ref_type_name(name string) bool { trimmed := name.trim_space() if trimmed == '' { return false } if trimmed.starts_with('&') || trimmed.starts_with('[]') || trimmed.starts_with('map[') || trimmed.starts_with('fn') || trimmed.starts_with('thread ') || trimmed in ['array', 'map', 'module', 'range', 'tuple', 'type', 'void'] { return true } if trimmed.starts_with('?') || trimmed.starts_with('!') { return e.is_ref_type_name(trimmed[1..]) } if e.is_builtin_type_name(trimmed) { return false } return trimmed.contains('.') } fn (e &Eval) sizeof_type_name(name string) i64 { trimmed := name.trim_space() if trimmed == '' { return 0 } if trimmed.starts_with('?') || trimmed.starts_with('!') { return e.sizeof_type_name(trimmed[1..]) } if trimmed.starts_with('&') || trimmed.ends_with('*') || trimmed == 'voidptr' || trimmed == 'byteptr' || trimmed.starts_with('fn') { return 8 } if trimmed.starts_with('[]') || trimmed == 'array' || trimmed.ends_with('Builder') || trimmed.ends_with('.Builder') { return 24 } if trimmed.starts_with('map[') || trimmed == 'map' { return 32 } if trimmed.starts_with('chan ') || trimmed.starts_with('thread ') { return 8 } return match trimmed { 'bool', 'byte', 'char', 'i8', 'u8' { 1 } 'i16', 'u16' { 2 } 'f32', 'i32', 'int', 'rune', 'u32' { 4 } 'f64', 'i64', 'isize', 'u64', 'usize' { 8 } 'string' { 16 } 'flags' { 4 } 'module', 'range', 'tuple', 'type', 'void' { 8 } else { if trimmed.contains('.') || trimmed == 'struct' { 8 } else { 0 } } } } fn (e &Eval) cast_value(value Value, type_name string) !Value { if info := e.sum_type_info(type_name) { if e.is_existing_sumtype_wrapper(value, type_name) { return value } if value is StructValue { if inferred := e.infer_bare_sumtype_variant(info, value) { payload := StructValue{ type_name: inferred.variant_name fields: value.fields.clone() } return e.build_sumtype_wrapper(type_name, info, inferred.tag, payload) } } if nested := e.infer_nested_sumtype_variant(info, value) { return e.build_sumtype_wrapper(type_name, info, nested.tag, nested.payload) } if tag := e.sumtype_tag_from_value(info, value) { return e.build_sumtype_wrapper(type_name, info, tag, value) } } match type_name { 'bool' { return e.value_as_bool(value)! } 'byte', 'char', 'i8', 'i16', 'int', 'i64', 'rune', 'u8', 'u16', 'u32', 'u64' { return e.value_as_int(value)! } 'f32', 'f64' { return e.value_as_f64(value)! } 'string' { return e.value_string(value) } else { if value is StructValue { if payload := e.unwrap_sumtype_value(value, type_name) { return payload } return StructValue{ type_name: type_name fields: value.fields.clone() } } return value } } } fn (e &Eval) type_name_matches(actual string, expected string) bool { if actual == expected { return true } mut actual_module := '' if actual.contains('.') { actual_module = actual.all_before_last('.') } for alias in e.type_name_aliases(actual_module, actual) { if alias == expected { return true } } mut expected_module := '' if expected.contains('.') { expected_module = expected.all_before_last('.') } for alias in e.type_name_aliases(expected_module, expected) { if alias == actual { return true } } return false } fn (e &Eval) value_matches_type_name(value Value, target_name string) bool { match value { ArrayValue { return target_name == 'array' || target_name.starts_with('[]') } FlagsValue { return target_name == 'flags' } MapValue { return target_name == 'map' || target_name.starts_with('map[') } ModuleValue { return target_name == 'module' } RangeValue { return target_name == 'range' } StructValue { return e.type_name_matches(value.type_name, target_name) } TupleValue { return target_name == 'tuple' } TypeValue { return target_name == 'type' || value.name == target_name } VoidValue { return target_name in ['none', 'nil', 'void'] } bool { return target_name == 'bool' } f64 { return target_name in ['f32', 'f64'] } i64 { return target_name in ['byte', 'char', 'i8', 'i16', 'i32', 'int', 'i64', 'isize', 'rune', 'u8', 'u16', 'u32', 'u64', 'usize'] } string { return target_name == 'string' } } } fn (e &Eval) expect_string_arg(args []Value, index int) !string { if index >= args.len { return error('v2.eval: missing argument ${index}') } value := args[index] if value is string { return value } return e.value_string(value) } fn (e &Eval) value_to_string_array(value Value) ![]string { if value is ArrayValue { vals := value.values return vals.map(e.value_string(it)) } return error('v2.eval: expected []string-compatible value') } fn (e &Eval) value_to_byte_array(value Value) ![]u8 { if value !is ArrayValue { return error('v2.eval: expected []u8-compatible value') } array_value := value as ArrayValue mut bytes := []u8{cap: array_value.values.len} for item in array_value.values { bytes << u8(e.value_as_int(item)!) } return bytes } fn (e &Eval) array_args_to_strings(args []Value) []string { mut out := []string{} for arg in args { if arg is ArrayValue { for item in arg.values { out << e.value_string(item) } continue } out << e.value_string(arg) } return out } fn (e &Eval) value_as_bool(value Value) !bool { return match value { bool { value } i64 { value != 0 } f64 { value_f64_payload(value) != 0.0 } string { value.len > 0 } ArrayValue { value.values.len > 0 } FlagsValue { false } MapValue { value.entries.len > 0 } StructValue { value.fields.len > 0 } VoidValue { false } else { return error('v2.eval: `${e.runtime_type_name(value)}` can not be used as bool') } } } fn (e &Eval) value_as_int(value Value) !i64 { return match value { i64 { value } f64 { i64(value_f64_payload(value)) } bool { if value { i64(1) } else { i64(0) } } string { strconv.parse_int(value, 0, 64)! } FlagsValue { i64(0) } else { return error('v2.eval: `${e.runtime_type_name(value)}` can not be used as int in `${e.current_function_label()}` stack `${e.call_stack_trace()}`') } } } fn (e &Eval) value_as_f64(value Value) !f64 { return match value { f64 { value_f64_payload(value) } i64 { f64(value) } bool { if value { 1.0 } else { 0.0 } } string { strconv.atof64(value)! } else { return error('v2.eval: `${e.runtime_type_name(value)}` can not be used as float') } } } fn (e &Eval) map_entry_string(entry MapEntry) string { key_text := if entry.key is string { "'${entry.key}'" } else { e.value_string(entry.key) } return '${key_text}: ${e.value_string(entry.value)}' } fn (e &Eval) value_string(value Value) string { return match value { ArrayValue { vals := value.values '[' + vals.map(e.value_string(it)).join(', ') + ']' } FlagsValue { 'flags' } MapValue { '{' + value.entries.map(e.map_entry_string(it)).join(', ') + '}' } ModuleValue { value.name } RangeValue { rs := value.start re := value.end sep := if value.inclusive { '...' } else { '..' } '${rs}${sep}${re}' } StructValue { keys := value.fields.keys() '${value.type_name}{' + keys.map('${it}: ${e.value_string(value.fields[it] or { void_value() })}').join(', ') + '}' } TupleValue { vals := value.values '(' + vals.map(e.value_string(it)).join(', ') + ')' } TypeValue { value.name } VoidValue { '' } bool, i64, string { value.str() } f64 { value_f64_payload(value).str() } } } fn (e &Eval) runtime_type_name(value Value) string { return match value { ArrayValue { 'array' } FlagsValue { 'flags' } MapValue { 'map' } ModuleValue { 'module' } RangeValue { 'range' } StructValue { value.type_name } TupleValue { 'tuple' } TypeValue { 'type' } VoidValue { 'void' } bool { 'bool' } f64 { 'f64' } i64 { 'int' } string { 'string' } } } fn (mut e Eval) write_stdout(s string) { if e.capture_output { e.stdout_data += s } else { print(s) } } fn (mut e Eval) write_stderr(s string) { if e.capture_output { e.stderr_data += s } else { eprint(s) } }