Files
agila_open_tool/tools/v/vlib/v2_toberemoved/eval/eval.v
T

6245 lines
150 KiB
V

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