1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

all: initial interpreter code (#12605)

This commit is contained in:
crthpl 2021-12-08 00:09:10 -08:00 committed by GitHub
parent 525791fa3a
commit 2fbf7fea75
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 3471 additions and 104 deletions

View File

@ -123,7 +123,8 @@ fn main() {
}
else {}
}
if command in ['run', 'build', 'build-module'] || command.ends_with('.v') || os.exists(command) {
if command in ['run', 'build', 'build-module', 'interpret'] || command.ends_with('.v')
|| os.exists(command) {
// println('command')
// println(prefs.path)
builder.compile(command, prefs)

View File

@ -1,18 +1,17 @@
// This program displays the fibonacci sequence
// import os
import os
fn main() {
// Check for user input
// if os.args.len != 2 {
// println('usage: fibonacci [rank]')
if os.args.len != 2 {
println('usage: fibonacci [rank]')
// Exit
// return
// }
return
}
// Parse first argument and cast it to int
// stop := os.args[1].int()
stop := 23
stop := os.args[1].int()
// Can only calculate correctly until rank 92
if stop > 92 {
println('rank must be 92 or less')
@ -20,9 +19,9 @@ fn main() {
}
// Three consecutive terms of the sequence
mut a := 0
mut b := 0
mut c := 1
mut a := i64(0)
mut b := i64(0)
mut c := i64(1)
println(a + c + c)
for _ in 0 .. stop {
// Set a and b to the next term

View File

@ -957,6 +957,7 @@ pub mut:
key_type Type
val_type Type
cond_type Type
high_type Type
kind Kind // array/map/string
label string // `label: for {`
scope &Scope

View File

@ -719,11 +719,11 @@ fn (mut b Builder) cc_linux_cross() {
verror(cc_res.output)
return
}
mut linker_args := ['-L $sysroot/usr/lib/x86_64-linux-gnu/', '--sysroot=$sysroot', '-v',
'-o $b.pref.out_name', '-m elf_x86_64',
mut linker_args := ['-L$sysroot/usr/lib/x86_64-linux-gnu/', '-L$sysroot/lib/x86_64-linux-gnu',
'--sysroot=$sysroot', '-v', '-o $b.pref.out_name', '-m elf_x86_64',
'-dynamic-linker /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2',
'$sysroot/crt1.o $sysroot/crti.o $obj_file', '-lc', '-lcrypto', '-lssl', '-lpthread',
'$sysroot/crtn.o']
'$sysroot/crtn.o', '-lm']
linker_args << cflags.c_options_only_object_files()
// -ldl
b.dump_c_options(linker_args)

View File

@ -8,6 +8,8 @@ import os
import rand
import v.pref
import v.util
import v.eval
import v.checker
fn (mut b Builder) get_vtmp_filename(base_file_name string, postfix string) string {
vtmp := util.get_vtmp_folder()
@ -42,6 +44,7 @@ pub fn compile(command string, pref &pref.Preferences) {
.c { b.compile_c() }
.js_node, .js_freestanding, .js_browser { b.compile_js() }
.native { b.compile_native() }
.interpret { b.interpret() }
}
mut timers := util.get_timers()
timers.show_remaining()
@ -116,7 +119,6 @@ fn (mut b Builder) run_compiled_executable_and_exit() {
panic('Running iOS apps is not supported yet.')
}
if b.pref.is_verbose {
println('============ running $b.pref.out_name ============')
}
if b.pref.is_test || b.pref.is_run {
compiled_file := os.real_path(b.pref.out_name)
@ -347,3 +349,15 @@ pub fn (v &Builder) get_user_files() []string {
}
return user_files
}
pub fn (mut b Builder) interpret() {
mut files := b.get_builtin_files()
files << b.get_user_files()
b.set_module_lookup_paths()
b.front_and_middle_stages(files) or { return }
util.timing_start('INTERPRET')
mut e := eval.new_eval(b.table, b.pref)
e.eval(b.parsed_files)
util.timing_measure('INTERPRET')
}

View File

@ -234,7 +234,8 @@ fn (mut c Checker) check_shift(mut node ast.InfixExpr, left_type ast.Type, right
// a negative value, the resulting value is implementation-defined (ID).
left_sym_final := c.table.get_final_type_symbol(left_type)
left_type_final := ast.Type(left_sym_final.idx)
if node.op == .left_shift && left_type_final.is_signed() {
if node.op == .left_shift && left_type_final.is_signed() && !(c.inside_unsafe
&& c.file.path.contains('vlib/v/eval/infix.v')) {
c.note('shifting a value from a signed type `$left_sym_final.name` can change the sign',
node.left.position())
}

View File

@ -28,7 +28,8 @@ const (
valid_comptime_if_platforms = ['amd64', 'i386', 'aarch64', 'arm64', 'arm32', 'rv64', 'rv32']
valid_comptime_if_cpu_features = ['x64', 'x32', 'little_endian', 'big_endian']
valid_comptime_if_other = ['js', 'debug', 'prod', 'test', 'glibc', 'prealloc',
'no_bounds_checking', 'freestanding', 'threads', 'js_node', 'js_browser', 'js_freestanding']
'no_bounds_checking', 'freestanding', 'threads', 'js_node', 'js_browser', 'js_freestanding',
'interpreter']
valid_comptime_not_user_defined = all_valid_comptime_idents()
array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice',
'sort', 'contains', 'index', 'wait', 'any', 'all', 'first', 'last', 'pop']
@ -4881,6 +4882,7 @@ fn (mut c Checker) for_in_stmt(mut node ast.ForInStmt) {
} else {
node.val_type = high_type
}
node.high_type = high_type
node.scope.update_var_type(node.val_var, node.val_type)
} else {
sym := c.table.get_final_type_symbol(typ)
@ -7208,6 +7210,7 @@ fn (mut c Checker) comptime_if_branch(cond ast.Expr, pos token.Position) bool {
'prealloc' { return !c.pref.prealloc }
'no_bounds_checking' { return cname !in c.pref.compile_defines_all }
'freestanding' { return !c.pref.is_bare || c.pref.output_cross_c }
'interpreter' { c.pref.backend != .interpret }
else { return false }
}
} else if cname !in c.pref.compile_defines_all {

View File

@ -4,96 +4,198 @@
module eval
import v.ast
import v.checker
import v.pref
import v.util
pub type Object = int | string
pub fn new_eval(table &ast.Table, pref &pref.Preferences) Eval {
return Eval{
table: table
pref: pref
}
}
// const/global is `Object`
type Symbol = Object | ast.EmptyStmt | ast.FnDecl
pub struct Eval {
mut:
checker checker.Checker
vars map[string]Var
table &ast.Table
pref &pref.Preferences
pub mut:
table &ast.Table
mods map[string]map[string]Symbol
future_register_consts map[string]map[string]map[string]ast.ConstField // mod:file:name:field
local_vars map[string]Var
local_vars_stack []map[string]Var
scope_idx int // this is increased when e.open_scope() is called, decreased when e.close_scope() (and all variables with that scope level deleted)
returning bool
return_values []Object
cur_mod string
cur_file string
back_trace []string
}
pub struct Var {
value Object
pub fn (mut e Eval) eval(files []&ast.File) {
e.register_symbols(files)
// println(files.map(it.path_base))
e.run_func(e.mods['main']['main'] or { ast.EmptyStmt{} } as ast.FnDecl)
}
pub fn (mut e Eval) eval(file ast.File, table &ast.Table) string {
vpref := &pref.Preferences{}
e.table = table
mut res := ''
e.checker = checker.new_checker(table, vpref)
for stmt in file.stmts {
res += e.stmt(stmt) + '\n'
// first arg is reciever (if method)
pub fn (mut e Eval) run_func(func ast.FnDecl, _args ...Object) {
e.back_trace << func.name
old_mod := e.cur_mod
e.cur_mod = func.mod
old_file := e.cur_file
e.cur_file = func.file
defer {
e.cur_mod = old_mod
e.cur_file = old_file
e.back_trace.pop()
}
return res.trim_space()
}
fn print_object(o Object) {
match o {
int { println(o) }
else { println('unknown object') }
mut args := _args.clone()
if func.params.len != args.len && !func.is_variadic {
e.error('mismatched parameter length for $func.name: got `$args.len`, expected `$func.params.len`')
}
}
pub fn (o Object) str() string {
match o {
int { return o.str() }
else { println('unknown object') }
}
return ''
}
fn (mut e Eval) stmt(node ast.Stmt) string {
match node {
ast.AssignStmt {
// TODO; replaced VarDecl
if func.name in ['print', 'println', 'eprint', 'eprintln'] {
s := args[0].string() // stringify because println accepts anything as argument
match func.name {
'print' {
print(s)
}
'println' {
println(s)
}
'eprint' {
eprint(s)
}
'eprintln' {
eprintln(s)
}
else {}
}
ast.ExprStmt {
o := e.expr(node.expr)
print('out: ')
print_object(o)
return o.str()
}
// ast.StructDecl {
// println('s decl')
// }
// ast.VarDecl {
// e.vars[it.name] = Var{
// value: e.expr(it.expr)
// }
// }
else {}
}
return '>>'
}
fn (mut e Eval) expr(node ast.Expr) Object {
match node {
ast.IntegerLiteral {
return node.val
}
ast.Ident {
print_object(node.value)
// Find the variable
v := e.vars[node.name]
return v.value
}
ast.InfixExpr {
e.checker.infix_expr(mut node)
// println('bin $it.op')
left := e.expr(node.left) as int
right := e.expr(node.right) as int
match node.op {
.plus { return left + right }
.mul { return left * right }
else {}
} else {
e.local_vars_stack << e.local_vars
e.local_vars = {}
old_scope := e.scope_idx
e.scope_idx = 0
e.open_scope()
// have to do this because of cgen error
args__ := if func.is_method { args[1..] } else { args }
for i, arg in args__ {
e.local_vars[(func.params[i]).name] = Var{
val: arg
scope_idx: e.scope_idx
}
}
else {}
if func.is_method {
print(e.back_trace)
println(func.receiver.typ - 65536)
e.local_vars[func.receiver.name] = Var{
val: args[0]
scope_idx: e.scope_idx
}
}
e.stmts(func.stmts)
e.returning = false
e.close_scope()
e.scope_idx = old_scope
e.local_vars = e.local_vars_stack.pop()
}
return 0
// return Object{}
}
pub fn (mut e Eval) register_symbols(files []&ast.File) {
for file in files {
// eprintln('registering file: $file.path_base')
mod := file.mod.name
e.register_symbol_stmts(file.stmts[1..], mod, file.path)
// eprintln('registered file: $file.path_base')
}
for mod, const_files in e.future_register_consts {
e.cur_mod = mod
for file, fields in const_files {
e.cur_file = file
for _, field in fields {
e.mods[mod][field.name.all_after_last('.')] = e.expr(field.expr, field.typ)
if mod == 'os' && field.name.all_after_last('.') == 'args' {
mut res := Array{}
res.val << e.pref.out_name.all_after_last('/')
for arg in e.pref.run_args {
res.val << arg
}
e.mods[mod][field.name.all_after_last('.')] = Object(res)
}
}
}
}
}
pub fn (mut e Eval) register_symbol_stmts(stmts []ast.Stmt, mod string, file string) {
for stmt in stmts { // first is just module declaration, so ignore
e.register_symbol(stmt, mod, file)
}
}
pub fn (mut e Eval) register_symbol(stmt ast.Stmt, mod string, file string) {
match stmt {
ast.FnDecl {
// this mess because c error
x := ast.Stmt(stmt)
y := Symbol(x as ast.FnDecl)
e.mods[mod][stmt.name.all_after_last('.')] = y
}
ast.Import {} // already handled by builder, TODO: get `as` name
ast.StructDecl {} // these are already parsed by the checker into e.table
ast.InterfaceDecl {}
ast.EnumDecl {}
ast.TypeDecl {}
ast.GlobalDecl {}
ast.HashStmt {}
ast.ConstDecl {
// evaluate them later since they may use functions defined after this point
for field in stmt.fields {
e.future_register_consts[mod][file][field.name] = field
}
}
ast.ExprStmt {
println('expr')
x := stmt.expr
match x {
ast.IfExpr {
if !x.is_comptime {
e.error('only comptime ifs are allowed in top level')
}
for i, branch in x.branches {
mut do_if := false
println('branch:$branch')
match (branch.cond as ast.Ident).name {
'windows' {
do_if = e.pref.os == .windows
}
else {
e.error('unknown compile time if')
}
}
do_if = do_if || x.branches.len == i + 1
if do_if {
e.register_symbol_stmts(branch.stmts, mod, file)
break
}
}
}
else {
e.error('unknown decleration expression statement $x.type_name()')
}
}
}
else {
e.error('unhandled decleration statement $stmt.type_name()')
}
}
}
fn (e Eval) error(msg string) {
util.verror('interpreter', msg)
}

566
vlib/v/eval/expr.v Normal file
View File

@ -0,0 +1,566 @@
module eval
import v.ast
import v.util
import math
import strconv
pub fn (mut e Eval) expr(expr ast.Expr, expecting ast.Type) Object {
match expr {
ast.CallExpr {
// println(expr.is_method)
// is_method := expr.left.type_name() != 'unknown v.ast.Expr'
// println(is_method)
if expr.name == 'int' {
e.error('methods not supported')
}
mut args := expr.args.map(e.expr(it.expr, it.typ))
if expr.is_method {
args.prepend(e.expr(expr.left, expr.receiver_type))
}
match expr.language {
.c {
if expr.is_method {
e.error('c does not have methods')
}
match expr.name.all_after('C.') {
'write' {
return Int{C.write(args[0].int_val(), args[1] as voidptr,
args[2].int_val()), 32}
}
'calloc' {
return Ptr{
val: vcalloc(int(args[0].int_val() * args[1].int_val()))
}
}
'getcwd' {
unsafe {
return Ptr{
val: C.getcwd((args[0] as Ptr).val as voidptr, args[1].int_val())
}
}
}
'memcpy' {
unsafe {
return Ptr{
val: C.memcpy(args[0] as voidptr, args[1] as voidptr,
args[2].int_val())
}
}
}
// 'printf' {
// mut voidptr_args := []voidptr{}
// for arg in args[1..] {
// // if arg is Int {
// // voidptr_args << voidptr(arg.val)
// // } else {
// voidptr_args << voidptr(&arg)
// // }
// }
// // println((e.local_vars['s'].val as string).str == voidptr_args[1])
// println('helo?$voidptr_args')
// // println((byteptr(voidptr_args[1])[0]))
// x := strconv.v_sprintf(args[0] as string, ...voidptr_args)
// // println('helo!')
// // println(x.len)
// y := C.write(1, x.str, x.len)
// println('aft')
// return Int{y, 32}
// }
else {
e.error('unknown c function: `$expr.name`')
}
}
}
.v {
// TODO: Anon functions
name := expr.name.all_after_last('.')
mod := expr.mod
mut func := e.mods[mod][name] or {
e.mods['builtin'][name] or { ast.EmptyStmt{} }
}
if func is ast.FnDecl {
e.run_func(func as ast.FnDecl, ...args)
if e.return_values.len == 1 {
return e.return_values[0]
} else {
return e.return_values
}
}
e.error('unknown function: ${mod}.$name at line $expr.pos.line_nr')
}
// .js {
// e.error('js is not supported')
// }
else {
e.error('$expr.language is not supported as a call expression language')
}
}
}
ast.StringLiteral {
// escape the escapes
mut res := ''
mut is_escape := false
for c in expr.val {
if is_escape {
res += e.get_escape(rune(c)).str()
} else if c == `\\` {
is_escape = true
} else {
res += rune(c).str()
}
}
return Object(res)
}
ast.IfExpr {
if expr.is_expr {
e.error('`if` expressions not supported')
}
if expr.is_comptime {
for i, branch in expr.branches {
mut do_if := false
if expr.has_else && i + 1 == expr.branches.len { // else branch
do_if = true
} else {
if branch.cond is ast.Ident {
match branch.cond.name {
'windows' {
do_if = e.pref.os == .windows
}
'macos' {
do_if = e.pref.os == .macos
}
'linux' {
do_if = e.pref.os == .linux
}
'android' {
do_if = e.pref.os == .android
}
'freebsd' {
do_if = e.pref.os == .freebsd
}
'prealloc' {
do_if = e.pref.prealloc
}
else {
e.error('unknown compile time if: $branch.cond.name')
}
}
} else if branch.cond is ast.PostfixExpr {
do_if = (branch.cond.expr as ast.Ident).name in e.pref.compile_defines
}
}
if do_if {
e.stmts(branch.stmts)
break
}
}
return empty
} else {
for i, b in expr.branches {
mut result := e.expr(b.cond, ast.bool_type_idx)
if expr.has_else && i + 1 == expr.branches.len { // else block
e.stmts(b.stmts)
break
}
if result is bool {
if result as bool {
e.stmts(b.stmts)
break
}
} else {
e.error('non-bool expression: $b.cond')
}
}
return empty
}
}
ast.InfixExpr {
left := e.expr(expr.left, expr.left_type)
right := e.expr(expr.right, expr.right_type)
return e.infix_expr(left, right, expr.op, expecting)
}
ast.IntegerLiteral {
// return u64(strconv.parse_uint(expr.val, 0, 64)
return i64(strconv.parse_int(expr.val, 0, 64) or {
e.error('invalid integer literal: $expr.val')
}) // TODO: numbers larger than 2^63 (for u64)
}
ast.FloatLiteral {
return f64(strconv.atof64(expr.val))
}
ast.BoolLiteral {
return expr.val
}
ast.Ident {
match expr.kind {
.variable {
// println(e.local_vars[expr.name].val.type_name())
return e.local_vars[expr.name].val
}
.constant {
return if expr.name.contains('.') {
e.mods[expr.name.all_before_last('.')]
} else {
e.mods[e.cur_mod]
}[expr.name.all_after_last('.')] or { ast.EmptyStmt{} } as Object
}
else {
e.error('unknown ident kind for `$expr.name`: $expr.kind')
}
}
}
ast.CastExpr {
x := e.expr(expr.expr, expr.expr_type)
if expr.typ in ast.signed_integer_type_idxs {
match x {
Uint {
return Int{
val: i64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
Int {
return Int{
val: i64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
Float {
return Int{
val: i64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
i64 {
if expecting in ast.signed_integer_type_idxs {
return Int{
val: x
size: i8(e.type_to_size(expecting))
}
} else {
return Uint{
val: u64(x)
size: i8(e.type_to_size(expecting))
}
}
}
f64 {
if expecting in ast.signed_integer_type_idxs {
return Int{
val: i64(x)
size: i8(e.type_to_size(expecting))
}
} else {
return Uint{
val: u64(x)
size: i8(e.type_to_size(expecting))
}
}
}
else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
} else if expr.typ in ast.unsigned_integer_type_idxs {
match x {
Uint {
return Uint{
val: u64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
Int {
return Uint{
val: u64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
Float {
return Uint{
val: u64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
i64 {
return Uint{
val: u64(x)
size: i8(e.type_to_size(expr.typ))
}
}
f64 {
if expecting in ast.signed_integer_type_idxs {
return Int{
val: i64(x)
size: i8(e.type_to_size(expecting))
}
} else {
return Uint{
val: u64(x)
size: i8(e.type_to_size(expecting))
}
}
}
else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
} else if expr.typ in ast.float_type_idxs {
match x {
Uint {
return Float{
val: f64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
Int {
return Float{
val: f64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
Float {
return Float{
val: f64(x.val)
size: i8(e.type_to_size(expr.typ))
}
}
f64 {
return Float{
val: x
size: i8(e.type_to_size(expr.typ))
}
}
else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
} else if expr.typ in ast.pointer_type_idxs {
y := *(x as Ptr).val
if expr.typ == ast.byteptr_type_idx {
match y {
char, voidptr {
unsafe {
return Ptr{
val: (x as Ptr).val
}
}
}
else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
} else if expr.typ == ast.voidptr_type_idx {
match y {
char, Int {
unsafe {
return Object(voidptr((x as Ptr).val))
}
}
else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
} else if expr.typ == ast.charptr_type_idx {
match y {
voidptr, Int {
unsafe {
return Ptr{
val: &char((x as Ptr).val)
}
}
}
else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
}
} else if e.table.get_type_symbol(expr.typ).kind in [.interface_, .sum_type] {
eprintln(util.formatted_error('warning:', 'sumtype or interface casts return void currently',
e.cur_file, expr.pos))
} else {
e.error('unknown cast: ${e.table.get_type_symbol(expr.expr_type).str()} to ${e.table.get_type_symbol(expr.typ).str()}')
}
}
ast.SelectorExpr {
exp := e.expr(expr.expr, expr.expr_type)
match exp {
string {
match expr.field_name {
'str' {
return Ptr{
val: exp.str
}
}
'len' {
return Int{exp.len, 32}
}
else {
e.error('unknown selector to string: $expr.field_name')
}
}
}
Array {
match expr.field_name {
'len' {
return Int{exp.val.len, 32}
}
else {
e.error('unknown selector to array: $expr.field_name')
}
}
}
else {
e.error('unknown selector expression: $exp.type_name()')
}
}
e.error(exp.str())
}
ast.ArrayInit {
if expr.has_len || expr.has_cap || expr.has_default {
if expr.has_len && !expr.has_cap && expr.has_default {
return Array{
val: []Object{len: int((e.expr(expr.len_expr, 7) as Int).val), init: e.expr(expr.default_expr,
expr.elem_type)}
}
} else if !expr.has_len && expr.has_cap && !expr.has_default {
return Array{
val: []Object{cap: int((e.expr(expr.cap_expr, 7) as Int).val)}
}
} else if !expr.has_len && !expr.has_cap && !expr.has_default {
return Array{
val: []Object{}
}
} else {
e.error('unknown array init combination; len: $expr.has_len, cap: $expr.has_cap, init: $expr.has_default')
}
}
if expr.is_fixed || expr.has_val {
e.error('fixed arrays are not supported')
}
mut res := Array{
val: []Object{cap: expr.exprs.len}
}
for i, exp in expr.exprs {
res.val << e.expr(exp, expr.expr_types[i])
}
return res
}
ast.CharLiteral {
if expr.val.len !in [1, 2] {
e.error('invalid size of char literal: $expr.val.len')
}
if expr.val[0] == `\\` { // is an escape
return e.get_escape(rune(expr.val[1]))
} else {
return rune(expr.val[0])
}
}
ast.StructInit {
// eprintln('unhandled struct init at line $expr.pos.line_nr')
return 'helo'
}
ast.SizeOf {
return Uint{e.type_to_size(expr.typ), 64}
}
ast.ParExpr {
return e.expr(expr.expr, expecting)
}
ast.PrefixExpr {
match expr.op {
.amp {
x := e.expr(expr.right, expr.right_type)
return Ptr{
val: &x
}
}
else {
e.error('unhandled prefix expression $expr.op')
}
}
}
ast.PostfixExpr {
match expr.op {
.inc {
e.add(expr.expr, Int{1, 64})
return e.expr(expr.expr, ast.i64_type_idx)
}
.dec {
e.add(expr.expr, Int{-1, 64})
return e.expr(expr.expr, ast.i64_type_idx)
}
else {
e.error('unhandled postfix expression $expr.op')
}
}
}
ast.StringInterLiteral {
mut res := expr.vals[0]
for i, exp in expr.exprs {
res += e.expr(exp, expr.expr_types[i]).string()
res += expr.vals[i + 1]
}
return res
}
else {
e.error('unhandled expression $expr.type_name()')
}
}
return empty
}
fn (e Eval) type_to_size(typ ast.Type) u64 {
match typ {
ast.voidptr_type_idx, ast.byteptr_type_idx, ast.charptr_type_idx {
return u64(if e.pref.m64 {
64
} else {
32
})
}
ast.i8_type_idx, ast.i16_type_idx, ast.int_type_idx, ast.i64_type_idx {
return u64(math.exp2(f64(typ - 2))) // this formula converts the type number to the bitsize
}
ast.byte_type_idx, ast.u16_type_idx, ast.u32_type_idx, ast.u64_type_idx {
return u64(math.exp2(f64(typ - 6))) // this formula converts the type number to the bitsize
}
ast.int_literal_type_idx, ast.float_literal_type_idx {
return 64
}
ast.f32_type_idx, ast.f64_type_idx {
return u64(math.exp2(f64(typ - 8))) // this formula converts the type number to the bitsize
}
else {
e.error('type_to_size(): unknown type: ${e.table.get_type_symbol(typ).str()}')
return -1
}
}
}
fn (e Eval) get_escape(r rune) rune {
res := match r {
`\\` {
`\\`
}
`n` {
`\n`
}
`0` {
`\0`
}
else {
`e`
}
}
if res == `e` {
e.error('unknown escape: `$r`')
}
return res
}

141
vlib/v/eval/gen/infix_gen.v Normal file
View File

@ -0,0 +1,141 @@
// this file generates ../infix.v
module main
import strings
import os
const (
header = 'module eval
import v.token
import v.ast
fn(e Eval)infix_expr(left Object,right Object,op token.Kind,expecting ast.Type)Object{match op{'
footer = "else{e.error('unknown infix expression: \$op')}}return empty // should e.error before this anyway
}
"
uk_expect_footer = "else{e.error('unknown infix expectation: \${e.table.get_type_symbol(expecting).str()}')}}"
comparison = {
'gt': '>'
'lt': '<'
'eq': '=='
'ne': '!='
}
math_ops = {
'plus': '+'
'minus': '*'
'mul': '+'
'div': '+'
'right_shift': '>>'
'left_shift': '<<'
}
compound_types = ['Int', 'Uint', 'Float']
literal_types = ['i64', 'f64']
)
fn main() {
mut b := strings.new_builder(124000)
b.write_string(header)
for enm, op in comparison {
b.write_string('.$enm{match left{')
for ct in compound_types {
b.write_string('$ct {match right{')
for ct2 in compound_types {
b.write_string('$ct2{return left.val${op}right.val}')
}
for lt2 in literal_types {
b.write_string('$lt2{return left.val${op}right}')
}
b.write_string("else{e.error('invalid operands to $op: $ct and \$right.type_name()')}}}")
}
for lt in literal_types {
b.write_string('$lt {match right{')
for ct2 in compound_types {
b.write_string('$ct2{return left${op}right.val}')
}
for lt2 in literal_types {
b.write_string('$lt2{return left${op}right}')
}
b.write_string("else {e.error('invalid operands to $op: ")
b.write_string(if lt == 'i64' { 'int' } else { 'float' })
b.write_string(" literal and \$right.type_name()')}}}")
}
if op in ['==', '!='] {
b.write_string('string{match right{string{return left${op}right}else{e.error(\'invalid operands to $op: string and \$right.type_name()\')}}}')
}
b.write_string("else {e.error('invalid operands to $op: \$left.type_name() and \$right.type_name()')}}}")
}
for math, op in math_ops {
b.write_string('.$math{match left{')
for ct in compound_types {
if op in ['<<', '>>'] && ct == 'Float' {
continue
}
b.write_string('$ct {match right{')
for ct2 in compound_types {
if op in ['<<', '>>'] && ct2 == 'Float' {
continue
}
unsafe_start, unsafe_end := if op in ['<<', '>>'] { 'unsafe{', '}' } else { '', '' }
b.write_string('$ct2{if expecting in ast.signed_integer_type_idxs{return Int{i64(left.val)+i64(right.val),i8(e.type_to_size(expecting))}}else if expecting in ast.unsigned_integer_type_idxs{return Uint{u64(left.val)+u64(right.val),i8(e.type_to_size(expecting))}}else if expecting==ast.int_literal_type_idx{${unsafe_start}return i64(i64(left.val)${op}i64(right.val))$unsafe_end}')
if op !in ['<<', '>>'] {
b.write_string('else if expecting in ast.float_type_idxs{return Float{f64(left.val)${op}f64(right.val), i8(e.type_to_size(expecting))}}else if expecting==ast.float_literal_type_idx{return f64(f64(left.val)${op}f64(right.val))}')
}
b.write_string(uk_expect_footer)
}
for lt2 in literal_types {
if op in ['<<', '>>'] && lt2 == 'f64' {
continue
}
unsafe_start, unsafe_end := if op in ['<<', '>>'] { 'unsafe{', '}' } else { '', '' }
b.write_string('$lt2{if expecting in ast.signed_integer_type_idxs{return Int{i64(left.val)+i64(right),i8(e.type_to_size(expecting))}}else if expecting in ast.unsigned_integer_type_idxs{return Uint{u64(left.val)+u64(right),i8(e.type_to_size(expecting))}}else if expecting==ast.int_literal_type_idx{${unsafe_start}return i64(i64(left.val)${op}i64(right))$unsafe_end}')
if op !in ['<<', '>>'] {
b.write_string('else if expecting in ast.float_type_idxs{return Float{f64(left.val)${op}f64(right), i8(e.type_to_size(expecting))}}else if expecting==ast.float_literal_type_idx{return f64(f64(left.val)${op}f64(right))}')
}
b.write_string(uk_expect_footer)
}
b.write_string("else {e.error('invalid operands to $op: $ct and \$right.type_name()')}}}")
}
for lt in literal_types {
if op in ['<<', '>>'] && lt == 'f64' {
continue
}
b.write_string('$lt{match right{')
for ct2 in compound_types {
if op in ['<<', '>>'] && ct2 == 'Float' {
continue
}
unsafe_start, unsafe_end := if op in ['<<', '>>'] { 'unsafe{', '}' } else { '', '' }
b.write_string('$ct2{if expecting in ast.signed_integer_type_idxs{return Int{i64(left)+i64(right.val),i8(e.type_to_size(expecting))}}else if expecting in ast.unsigned_integer_type_idxs{return Uint{u64(left)+u64(right.val),i8(e.type_to_size(expecting))}}else if expecting==ast.int_literal_type_idx{${unsafe_start}return i64(i64(left)${op}i64(right.val))$unsafe_end}')
if op !in ['<<', '>>'] {
b.write_string('else if expecting in ast.float_type_idxs{return Float{f64(left)${op}f64(right.val), i8(e.type_to_size(expecting))}}else if expecting==ast.float_literal_type_idx{return f64(f64(left)${op}f64(right.val))}')
}
b.write_string(uk_expect_footer)
}
for lt2 in literal_types {
if op in ['<<', '>>'] && lt2 == 'f64' {
continue
}
unsafe_start, unsafe_end := if op in ['<<', '>>'] { 'unsafe{', '}' } else { '', '' }
b.write_string('$lt2{if expecting in ast.signed_integer_type_idxs{return Int{i64(left)+i64(right),i8(e.type_to_size(expecting))}}else if expecting in ast.unsigned_integer_type_idxs{return Uint{u64(left)+u64(right),i8(e.type_to_size(expecting))}}else if expecting==ast.int_literal_type_idx{${unsafe_start}return i64(i64(left)${op}i64(right))$unsafe_end}')
if op !in ['<<', '>>'] {
b.write_string('else if expecting in ast.float_type_idxs{return Float{f64(left)${op}f64(right), i8(e.type_to_size(expecting))}}else if expecting==ast.float_literal_type_idx{return f64(f64(left)${op}f64(right))}')
}
b.write_string(uk_expect_footer)
}
b.write_string("else {e.error('invalid operands to $op: ")
b.write_string(if lt == 'i64' { 'int' } else { 'float' })
b.write_string(" literal and \$right.type_name()')}}}")
}
b.write_string("else {e.error('invalid operands to $op: \$left.type_name() and \$right.type_name()')}}}")
}
b.write_string(footer)
path := @FILE.all_before(@FILE.all_after_last('/')) + '../infix.v'
os.write_file(path, b.str()) or { panic(err) }
res := os.execute(@VEXE + ' fmt -w ' + path)
if res.exit_code != 0 {
eprintln('v fmt failed!')
panic(res.output)
}
}

2211
vlib/v/eval/infix.v Normal file

File diff suppressed because it is too large Load Diff

150
vlib/v/eval/object.v Normal file
View File

@ -0,0 +1,150 @@
module eval
const empty = Void{}
// NB: i64 is an int_literal, NOT an i64 (same with f64)
type Object = Array
| Float
| Int
| Ptr
| Uint
| Void
| []Object
| bool
| byte
| char
| f64
| i64
| rune
| string
| voidptr
// string is the same as the autogenerated str() methods
pub fn (o Object) string() string {
match o {
byte {
panic('error: byte should only be used for &byte')
}
bool {
return o.str()
}
i64 { // int_literal
return o.str()
}
f64 { // float_literal
return o.str()
}
Int {
return o.val.str()
}
Uint {
return o.val.str()
}
Float {
return o.val.str()
}
string {
return o
}
Void {
return ''
}
[]Object {
mut res := '('
for i, obj in o {
res += obj.str()
if i + 1 != o.len {
res += ', '
}
}
return res + ')'
}
voidptr {
return o.str()
}
char {
return int(o).str()
}
rune {
return o.str()
}
Array {
mut res := '['
for i, val in o.val {
res += val.string()
if i + 1 != o.val.len {
res += ', '
}
}
return res + ']'
}
Ptr {
return o.val.str()
}
}
}
pub fn (o Object) int_val() i64 {
match o {
Int {
return o.val
}
Uint {
return i64(o.val)
}
i64 {
return o
}
else {
panic('Object.int_val(): not an int')
}
}
}
pub fn (o Object) float_val() f64 {
match o {
Float {
return o.val
}
f64 {
return o
}
else {
panic('Object.float_val(): not a float')
}
}
}
struct Void {}
pub struct Int {
pub mut:
val i64
size i8 // 8/16/32/64
}
pub struct Uint {
pub mut:
val u64
size i8 // 8/16/32/64
}
pub struct Float {
pub mut:
val f64
size i8 // 8/16/32/64
}
pub struct Array {
pub mut:
val []Object
}
pub struct Ptr {
val &Object
}
// override the autogenerated str, since it does not work
fn (p Ptr) str() string {
return u64(p.val).str()
}

93
vlib/v/eval/stmt.v Normal file
View File

@ -0,0 +1,93 @@
module eval
import v.ast
pub fn (mut e Eval) stmts(stmts []ast.Stmt) {
e.open_scope()
for stmt in stmts {
e.stmt(stmt)
if e.returning {
break
}
}
e.close_scope()
}
pub fn (mut e Eval) stmt(stmt ast.Stmt) {
match stmt {
ast.ExprStmt {
e.expr(stmt.expr, stmt.typ)
}
ast.AssignStmt {
// if stmt.left.len != 1 {
// e.error('multiple values assignments are not supported')
// }
mut rights := []Object{}
for i, right in stmt.right {
rights << e.expr(right, stmt.right_types[i])
}
if rights[0] is []Object { // needs to be unpacked
e.error('multiple assignment from function is not supported')
}
match stmt.op {
.decl_assign {
for i, left in stmt.left {
e.set(left, rights[i], true, stmt.left_types[i])
}
}
.assign {
for i, left in stmt.left {
e.set(left, rights[i], false, stmt.left_types[i])
}
}
else {
e.error('unknown assign statment: $stmt.op')
}
}
}
ast.Return {
e.returning = true
e.return_values = []
for i, expr in stmt.exprs {
e.return_values << e.expr(expr, stmt.types[i])
}
}
ast.ForInStmt {
if !stmt.is_range {
e.error('only range for in statements are supported')
}
if stmt.key_var != '' {
e.error('keys are not supported in for in statements')
}
e.open_scope()
underscore := stmt.val_var == '_'
if !underscore {
e.set(ast.Ident{ name: stmt.val_var, scope: 0 }, Int{-1, 32}, true, stmt.val_type)
}
for i in (e.expr(stmt.cond, ast.int_type_idx) as Int).val .. (e.expr(stmt.high,
ast.int_type_idx) as Int).val {
if !underscore {
e.set(ast.Ident{ name: stmt.val_var, scope: 0 }, Int{i, 32}, false,
stmt.val_type)
}
e.stmts(stmt.stmts)
}
e.close_scope()
}
ast.ForStmt {
for {
should_break := e.expr(stmt.cond, ast.bool_type_idx)
if !(should_break as bool) {
break
}
e.stmts(stmt.stmts)
}
}
ast.Block {
e.stmts(stmt.stmts)
}
else {
e.error('unhandled statement $stmt.type_name()')
}
}
}

68
vlib/v/eval/var.v Normal file
View File

@ -0,0 +1,68 @@
module eval
import v.ast
pub struct Var {
pub mut:
val Object
pub:
scope_idx int
typ ast.Type
}
pub fn (mut e Eval) open_scope() {
e.scope_idx++
}
pub fn (mut e Eval) close_scope() {
e.scope_idx--
for name, var in e.local_vars {
if var.scope_idx > e.scope_idx {
e.local_vars.delete(name)
}
}
}
pub fn (mut e Eval) set(expr ast.Expr, val Object, init bool, typ ast.Type) {
match expr {
ast.Ident {
if init {
e.local_vars[expr.name] = Var{
val: val
scope_idx: e.scope_idx
typ: typ
}
} else {
e.local_vars[expr.name].val = val
}
}
ast.IndexExpr {
panic('>>$expr.pos, $e.cur_file')
// if init {
// e.error('index init assignment')
// } else {
// mut x := (e.local_vars[(expr.left as ast.Ident).name].val)
// if x is Array {
// x.val[(e.expr(expr.index, ast.int_type_idx) as Int).val] = val
// }
// }
}
else {
panic('unknown left value to assign statment: $expr.type_name()')
}
}
}
// val and expr must be both numeric types, or both string
pub fn (mut e Eval) add(expr ast.Expr, val Object) {
match expr {
ast.Ident {
e.local_vars[expr.name].val = e.infix_expr(e.local_vars[expr.name].val, val,
.plus, e.local_vars[expr.name].typ)
}
else {
panic('unknown left value to add statment: $expr.type_name()')
}
}
}

View File

@ -49,17 +49,15 @@ pub enum Backend {
js_browser // The JavaScript browser backend
js_freestanding // The JavaScript freestanding backend
native // The Native backend
interpret // Interpret the ast
}
pub fn (b Backend) is_js() bool {
match b {
.js_node, .js_browser, .js_freestanding {
return true
}
else {
return false
}
}
return b in [
.js_node,
.js_browser,
.js_freestanding,
]
}
pub enum CompilerType {
@ -486,6 +484,9 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
res.backend = .native
res.build_options << arg
}
'-interpret' {
res.backend = .interpret
}
'-W' {
res.warns_are_errors = true
}
@ -707,6 +708,21 @@ pub fn parse_args(known_external_commands []string, args []string) (&Preferences
res.is_run = true
res.path = command
res.run_args = args[command_pos + 1..]
} else if command == 'interpret' {
res.backend = .interpret
if command_pos + 2 > args.len {
eprintln('v interpret: no v files listed')
exit(1)
}
res.path = args[command_pos + 1]
res.run_args = args[command_pos + 2..]
must_exist(res.path)
if !res.path.ends_with('.v') && os.is_executable(res.path) && os.is_file(res.path)
&& os.is_file(res.path + '.v') {
eprintln('It looks like you wanted to run "${res.path}.v", so we went ahead and did that since "$res.path" is an executable.')
res.path += '.v'
}
}
if command == 'build-module' {
res.build_mode = .build_module
@ -801,6 +817,7 @@ pub fn backend_from_string(s string) ?Backend {
'js_browser' { return .js_browser }
'js_freestanding' { return .js_freestanding }
'native' { return .native }
'interpret' { return .interpret }
else { return error('Unknown backend type $s') }
}
}

View File

@ -15,7 +15,7 @@ pub fn (prefs &Preferences) should_compile_filtered_files(dir string, files_ []s
|| file.all_before_last('.v').all_before_last('.').ends_with('_test') {
continue
}
if prefs.backend == .c && !prefs.should_compile_c(file) {
if prefs.backend in [.c, .interpret] && !prefs.should_compile_c(file) {
continue
}
if prefs.backend.is_js() && !prefs.should_compile_js(file) {