diff --git a/compiler/cgen.v b/compiler/cgen.v index ed115f76c3..bcb746901c 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -315,8 +315,9 @@ fn (v &V) type_definitions() string { // sort structs types_sorted := sort_structs(types) // Generate C code - return types_to_c(builtin_types,v.table) + '\n//----\n' + + res := types_to_c(builtin_types,v.table) + '\n//----\n' + types_to_c(types_sorted, v.table) + return res } // sort structs by dependant fields diff --git a/compiler/cheaders.v b/compiler/cheaders.v index 9715ed8213..956e52414b 100644 --- a/compiler/cheaders.v +++ b/compiler/cheaders.v @@ -116,13 +116,10 @@ typedef map map_string; #endif //============================== HELPER C MACROS =============================*/ - #define _PUSH(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push(arr, &tmp);} #define _PUSH_MANY(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push_many(arr, tmp.data, tmp.len);} #define _IN(typ, val, arr) array_##typ##_contains(arr, val) #define _IN_MAP(val, m) map__exists(m, val) -//#define ALLOC_INIT(type, ...) (type *)memdup((type[]){ __VA_ARGS__ }, sizeof(type)) - //================================== GLOBALS =================================*/ byteptr g_str_buf; int load_so(byteptr); diff --git a/compiler/fn.v b/compiler/fn.v index 8981e41cd4..b535581038 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -33,7 +33,34 @@ mut: //gen_types []string } -fn (f &Fn) find_var(name string) Var { +fn (f &Fn) find_var(name string) ?Var { + for i in 0 .. f.var_idx { + if f.local_vars[i].name == name { + return f.local_vars[i] + } + } + return none +} + +fn (p &Parser) find_var_check_new_var(name string) ?Var { + for i in 0 .. p.cur_fn.var_idx { + if p.cur_fn.local_vars[i].name == name { + return p.cur_fn.local_vars[i] + } + } + // A hack to allow `newvar := Foo{ field: newvar }` + // Declare the variable so that it can be used in the initialization + if name == 'main__' + p.var_decl_name { + return Var{ + name : p.var_decl_name + typ : 'voidptr' + is_mut : true + } + } + return none +} + +fn (f &Fn) find_var2(name string) Var { for i in 0 .. f.var_idx { if f.local_vars[i].name == name { return f.local_vars[i] @@ -73,8 +100,10 @@ fn (p mut Parser) mark_var_changed(v Var) { } fn (f mut Fn) known_var(name string) bool { - v := f.find_var(name) - return v.name.len > 0 + _ := f.find_var(name) or { + return false + } + return true } fn (f mut Fn) register_var(v Var) { @@ -819,9 +848,9 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { } p.check(.key_mut) var_name := p.lit - v := p.cur_fn.find_var(var_name) - if v.name == '' { + v := p.cur_fn.find_var(var_name) or { p.error('`$arg.name` is a mutable argument, you need to provide a variable to modify: `$f.name(... mut a...)`') + exit(1) } if !v.is_changed { p.mark_var_changed(v) diff --git a/compiler/main.v b/compiler/main.v index fbf11bd764..b0a80e65e2 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -184,6 +184,17 @@ fn main() { v.run_compiled_executable_and_exit() } + // TODO remove + if v.pref.autofree { + println('started freeing v struct') + v.table.fns.free() + v.table.typesmap.free() + v.table.obf_ids.free() + v.cgen.lines.free() + free(v.cgen) + free(v.table) + println('done!') + } } fn (v mut V) compile() { @@ -833,7 +844,7 @@ fn new_v(args[]string) &V { show_c_cmd: '-show_c_cmd' in args translated: 'translated' in args is_run: 'run' in args - autofree: 'autofree' in args + autofree: '-autofree' in args is_repl: is_repl build_mode: build_mode cflags: cflags diff --git a/compiler/parser.v b/compiler/parser.v index 772fd210c1..91ca3e43b7 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -1267,12 +1267,11 @@ fn ($v.name mut $v.typ) $p.cur_fn.name (...) { //p.warn('expecting array got $expr_type') //} // Allow `num = 4` where `num` is an `?int` - if p.assigned_type.starts_with('Option_') && expr_type == p.assigned_type.right('Option_'.len) { - println('allowing option asss') + if p.assigned_type.starts_with('Option_') && + expr_type == p.assigned_type.right('Option_'.len) { expr := p.cgen.cur_line.right(pos) left := p.cgen.cur_line.left(pos) typ := expr_type.replace('Option_', '') - //p.cgen.cur_line = left + 'opt_ok($expr)' p.cgen.resetln(left + 'opt_ok($expr, sizeof($typ))') } else if !p.builtin_mod && !p.check_types_no_throw(expr_type, p.assigned_type) { @@ -1319,7 +1318,7 @@ fn (p mut Parser) var_decl() { name: name typ: typ is_mut: is_mut - is_alloc: p.is_alloc + is_alloc: p.is_alloc || typ.starts_with('array_') }) //if p.is_alloc { println('REG VAR IS ALLOC $name') } p.var_decl_name = '' @@ -1486,44 +1485,37 @@ fn (p mut Parser) name_expr() string { name = p.prepend_mod(name) } // Variable - mut v := p.cur_fn.find_var(name) - // A hack to allow `newvar := Foo{ field: newvar }` - // Declare the variable so that it can be used in the initialization - if name == 'main__' + p.var_decl_name { - v.name = p.var_decl_name - v.typ = 'voidptr' - v.is_mut = true + for { // TODO remove + mut v := p.find_var_check_new_var(name) or { break } + if ptr { + p.gen('& /*vvar*/ ') } - if v.name.len != 0 { - if ptr { - p.gen('& /*vvar*/ ') - } - else if deref { - p.gen('*') - } - mut typ := p.var_expr(v) - // *var - if deref { - if !typ.contains('*') && !typ.ends_with('ptr') { - println('name="$name", t=$v.typ') - p.error('dereferencing requires a pointer, but got `$typ`') - } - typ = typ.replace('ptr', '')// TODO - typ = typ.replace('*', '')// TODO - } - // &var - else if ptr { - typ += '*' - } - if p.inside_return_expr { - //println('marking $v.name returned') - p.mark_var_returned(v) - // v.is_returned = true // TODO modifying a local variable - // that's not used afterwards, this should be a compilation - // error - } - return typ + else if deref { + p.gen('*') } + mut typ := p.var_expr(v) + // *var + if deref { + if !typ.contains('*') && !typ.ends_with('ptr') { + println('name="$name", t=$v.typ') + p.error('dereferencing requires a pointer, but got `$typ`') + } + typ = typ.replace('ptr', '')// TODO + typ = typ.replace('*', '')// TODO + } + // &var + else if ptr { + typ += '*' + } + if p.inside_return_expr { + //println('marking $v.name returned') + p.mark_var_returned(v) + // v.is_returned = true // TODO modifying a local variable + // that's not used afterwards, this should be a compilation + // error + } + return typ + } // TODO REMOVE for{} // if known_type || is_c_struct_init || (p.first_pass() && p.peek() == .lcbr) { // known type? int(4.5) or Color.green (enum) if p.table.known_type(name) { @@ -1881,7 +1873,7 @@ struct $f.parent_fn { } enum IndexType { - none + noindex str map array @@ -1898,7 +1890,7 @@ fn get_index_type(typ string) IndexType { return IndexType.ptr } if typ[0] == `[` { return IndexType.fixed_array } - return IndexType.none + return IndexType.noindex } fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { @@ -2239,7 +2231,14 @@ fn (p mut Parser) factor() string { mut typ := '' tok := p.tok switch tok { - case .number: + case .key_none: + if !p.expected_type.starts_with('Option_') { + p.error('need "$p.expected_type" got none') + } + p.gen('opt_none()') + p.check(.key_none) + return p.expected_type + case Token.number: typ = 'int' // Check if float (`1.0`, `1e+3`) but not if is hexa if (p.lit.contains('.') || (p.lit.contains('e') || p.lit.contains('E'))) && @@ -2367,10 +2366,10 @@ fn (p mut Parser) assoc() string { // println('assoc()') p.next() name := p.check_name() - if !p.cur_fn.known_var(name) { + var := p.cur_fn.find_var(name) or { p.error('unknown variable `$name`') - } - var := p.cur_fn.find_var(name) + exit(1) + } p.check(.pipe) p.gen('($var.typ){') mut fields := []string// track the fields user is setting, the rest will be copied from the old object @@ -3463,10 +3462,14 @@ fn (p mut Parser) return_st() { else { ph := p.cgen.add_placeholder() p.inside_return_expr = true + is_none := p.tok == .key_none + p.expected_type = p.cur_fn.typ expr_type := p.bool_expression() p.inside_return_expr = false - // Automatically wrap an object inside an option if the function returns an option - if p.cur_fn.typ.ends_with(expr_type) && p.cur_fn.typ.starts_with('Option_') { + // Automatically wrap an object inside an option if the function + // returns an option + if p.cur_fn.typ.ends_with(expr_type) && !is_none && + p.cur_fn.typ.starts_with('Option_') { tmp := p.get_tmp() ret := p.cgen.cur_line.right(ph) typ := expr_type.replace('Option_', '') @@ -3537,7 +3540,7 @@ fn (p mut Parser) go_statement() { // Method if p.peek() == .dot { var_name := p.lit - v := p.cur_fn.find_var(var_name) + v := p.cur_fn.find_var(var_name) or { return } p.mark_var_used(v) p.next() p.check(.dot) diff --git a/compiler/query.v b/compiler/query.v index d7565d14f0..4ab00bdc4a 100644 --- a/compiler/query.v +++ b/compiler/query.v @@ -2,9 +2,9 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -module main +module main -import strings +import strings fn sql_params2params_gen(sql_params []string, sql_types []string, qprefix string) string { mut params_gen := '' @@ -32,7 +32,7 @@ fn sql_params2params_gen(sql_params []string, sql_types []string, qprefix string return params_gen } -// `db.select from User where id == 1 && nr_bookings > 0` +// `db.select from User where id == 1 && nr_bookings > 0` fn (p mut Parser) select_query(fn_ph int) string { // NB: qprefix and { p.sql_i, p.sql_params, p.sql_types } SHOULD be reset for each query, // because we can have many queries in the _same_ scope. @@ -40,96 +40,96 @@ fn (p mut Parser) select_query(fn_ph int) string { p.sql_i = 0 p.sql_params = []string p.sql_types = []string - - mut q := 'select ' - p.check(.key_select) - n := p.check_name() + + mut q := 'select ' + p.check(.key_select) + n := p.check_name() if n == 'count' { - q += 'count(*) from ' - p.check_name() - } - table_name := p.check_name() - // Register this type's fields as variables so they can be used in where expressions - typ := p.table.find_type(table_name) + q += 'count(*) from ' + p.check_name() + } + table_name := p.check_name() + // Register this type's fields as variables so they can be used in where expressions + typ := p.table.find_type(table_name) if typ.name == '' { - p.error('unknown type `$table_name`') - } - //fields := typ.fields.filter(typ == 'string' || typ == 'int') - // get only string and int fields - mut fields := []Var + p.error('unknown type `$table_name`') + } + //fields := typ.fields.filter(typ == 'string' || typ == 'int') + // get only string and int fields + mut fields := []Var for i, field in typ.fields { - if field.typ != 'string' && field.typ != 'int' { + if field.typ != 'string' && field.typ != 'int' { continue - } - fields << field - } + } + fields << field + } if fields.len == 0 { - p.error('V orm: select: empty fields in `$table_name`') - } + p.error('V orm: select: empty fields in `$table_name`') + } if fields[0].name != 'id' { - p.error('V orm: `id int` must be the first field in `$table_name`') - } - // 'select id, name, age from...' + p.error('V orm: `id int` must be the first field in `$table_name`') + } + // 'select id, name, age from...' if n == 'from' { for i, field in fields { - q += field.name + q += field.name if i < fields.len - 1 { - q += ', ' - } - } - q += ' from ' - } + q += ', ' + } + } + q += ' from ' + } for field in fields { - //println('registering sql field var $field.name') - if field.typ != 'string' && field.typ != 'int' { + //println('registering sql field var $field.name') + if field.typ != 'string' && field.typ != 'int' { continue - } - p.cur_fn.register_var({ field | is_used:true }) - } - q += table_name - // `where` statement - if p.tok == .name && p.lit == 'where' { - p.next() - p.cgen.start_tmp() - p.is_sql = true - p.bool_expression() - p.is_sql = false - q += ' where ' + p.cgen.end_tmp() - } - // limit? - mut query_one := false - if p.tok == .name && p.lit == 'limit' { - p.next() - p.cgen.start_tmp() - p.is_sql = true - p.bool_expression() - p.is_sql = false - limit := p.cgen.end_tmp() - q += ' limit ' + limit - // `limit 1` means we are getting `?User`, not `[]User` - if limit.trim_space() == '1' { - query_one = true - } - } + } + p.cur_fn.register_var({ field | is_used:true }) + } + q += table_name + // `where` statement + if p.tok == .name && p.lit == 'where' { + p.next() + p.cgen.start_tmp() + p.is_sql = true + p.bool_expression() + p.is_sql = false + q += ' where ' + p.cgen.end_tmp() + } + // limit? + mut query_one := false + if p.tok == .name && p.lit == 'limit' { + p.next() + p.cgen.start_tmp() + p.is_sql = true + p.bool_expression() + p.is_sql = false + limit := p.cgen.end_tmp() + q += ' limit ' + limit + // `limit 1` means we are getting `?User`, not `[]User` + if limit.trim_space() == '1' { + query_one = true + } + } println('sql query="$q"') p.cgen.insert_before('// DEBUG_SQL prefix: $qprefix | fn_ph: $fn_ph | query: "$q" ') if n == 'count' { p.cgen.set_placeholder(fn_ph, 'pg__DB_q_int(') - p.gen(', tos2("$q"))') - } else { - // Build an object, assign each field. - tmp := p.get_tmp() - mut obj_gen := strings.new_builder(300) + p.gen(', tos2("$q"))') + } else { + // Build an object, assign each field. + tmp := p.get_tmp() + mut obj_gen := strings.new_builder(300) for i, field in fields { - mut cast := '' + mut cast := '' if field.typ == 'int' { - cast = 'v_string_int' - } + cast = 'v_string_int' + } obj_gen.writeln('${qprefix}$tmp . $field.name = $cast( *(string*)array__get(${qprefix}row.vals, $i) );') - } - // One object - if query_one { + } + // One object + if query_one { mut params_gen := sql_params2params_gen( p.sql_params, p.sql_types, qprefix ) p.cgen.insert_before(' @@ -144,99 +144,99 @@ if (! opt_${qprefix}row . ok ) { opt_${qprefix}$tmp = v_error( opt_${qprefix}row . error ); }else{ $table_name ${qprefix}$tmp; - pg__Row ${qprefix}row = *(pg__Row*) opt_${qprefix}row . data; + pg__Row ${qprefix}row = *(pg__Row*) opt_${qprefix}row . data; ${obj_gen.str()} opt_${qprefix}$tmp = opt_ok( & ${qprefix}$tmp, sizeof($table_name) ); } ') p.cgen.resetln('opt_${qprefix}$tmp') - } - // Array + } + // Array else { q += ' order by id' params_gen := sql_params2params_gen( p.sql_params, p.sql_types, qprefix ) p.cgen.insert_before('char* ${qprefix}params[$p.sql_i]; -$params_gen +$params_gen void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ; array_pg__Row ${qprefix}rows = pg__res_to_rows(${qprefix}res); -// TODO preallocate +// TODO preallocate array ${qprefix}arr_$tmp = new_array(0, 0, sizeof($table_name)); for (int i = 0; i < ${qprefix}rows.len; i++) { pg__Row ${qprefix}row = *(pg__Row*)array__get(${qprefix}rows, i); $table_name ${qprefix}$tmp; - ${obj_gen.str()} + ${obj_gen.str()} _PUSH(&${qprefix}arr_$tmp, ${qprefix}$tmp, ${tmp}2, $table_name); -} +} ') p.cgen.resetln('${qprefix}arr_$tmp') -} - - } +} + + } if n == 'count' { - return 'int' + return 'int' } else if query_one { opt_type := 'Option_$table_name' p.cgen.typedefs << 'typedef Option $opt_type;' p.table.register_type( opt_type ) return opt_type } else { - p.register_array('array_$table_name') - return 'array_$table_name' - } -} + p.register_array('array_$table_name') + return 'array_$table_name' + } +} -// `db.insert(user)` -fn (p mut Parser) insert_query(fn_ph int) { - p.check_name() - p.check(.lpar) - var_name := p.check_name() - p.check(.rpar) - var := p.cur_fn.find_var(var_name) - typ := p.table.find_type(var.typ) - mut fields := []Var +// `db.insert(user)` +fn (p mut Parser) insert_query(fn_ph int) { + p.check_name() + p.check(.lpar) + var_name := p.check_name() + p.check(.rpar) + var := p.cur_fn.find_var(var_name) or { return } + typ := p.table.find_type(var.typ) + mut fields := []Var for i, field in typ.fields { - if field.typ != 'string' && field.typ != 'int' { + if field.typ != 'string' && field.typ != 'int' { continue - } - fields << field - } + } + fields << field + } if fields.len == 0 { - p.error('V orm: insert: empty fields in `$var.typ`') - } + p.error('V orm: insert: empty fields in `$var.typ`') + } if fields[0].name != 'id' { - p.error('V orm: `id int` must be the first field in `$var.typ`') - } - table_name := var.typ - mut sfields := '' // 'name, city, country' - mut params := '' // params[0] = 'bob'; params[1] = 'Vienna'; - mut vals := '' // $1, $2, $3... - mut nr_vals := 0 + p.error('V orm: `id int` must be the first field in `$var.typ`') + } + table_name := var.typ + mut sfields := '' // 'name, city, country' + mut params := '' // params[0] = 'bob'; params[1] = 'Vienna'; + mut vals := '' // $1, $2, $3... + mut nr_vals := 0 for i, field in fields { if field.name == 'id' { - continue - } - sfields += field.name - vals += '$' + i.str() - nr_vals++ + continue + } + sfields += field.name + vals += '$' + i.str() + nr_vals++ params += 'params[${i-1}] = ' - if field.typ == 'string' { - params += '$var_name . $field.name .str;\n' + if field.typ == 'string' { + params += '$var_name . $field.name .str;\n' } else if field.typ == 'int' { - params += 'int_str($var_name . $field.name).str;\n' + params += 'int_str($var_name . $field.name).str;\n' } else { - p.error('V ORM: unsupported type `$field.typ`') - } - if i < fields.len - 1 { - sfields += ', ' - vals += ', ' - } - } - p.cgen.insert_before('char* params[$nr_vals];' + params) + p.error('V ORM: unsupported type `$field.typ`') + } + if i < fields.len - 1 { + sfields += ', ' + vals += ', ' + } + } + p.cgen.insert_before('char* params[$nr_vals];' + params) p.cgen.set_placeholder(fn_ph, 'PQexecParams( ') p.genln('.conn, "insert into $table_name ($sfields) values ($vals)", $nr_vals, -0, params, 0, 0, 0)') -} +0, params, 0, 0, 0)') +} diff --git a/compiler/table.v b/compiler/table.v index fb596731f2..32fe017cc6 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -78,6 +78,7 @@ mut: is_changed bool scope_level int is_c bool // todo remove once `typ` is `Type`, not string + moved bool } struct Type { @@ -322,6 +323,7 @@ fn (t &Table) find_fn(name string) Fn { if !isnil(f.name.str) { return f } + //println('ret find fn') return Fn{} } @@ -412,6 +414,7 @@ fn (t &Type) find_field(name string) Var { return field } } + //println('ret Var{}') return Var{} } @@ -455,11 +458,16 @@ fn (table &Table) find_method(typ &Type, name string) Fn { // method := typ.find_method(name) t := table.typesmap[typ.name] method := t.find_method(name) - if method.name.len == 0 && typ.parent.len > 0 { + + for method in t.methods { + if method.name == name { + return method + } + } + + if typ.parent != '' { parent := table.find_type(typ.parent) return parent.find_method(name) - // println('parent = $parent.name $res') - // return res } return method } @@ -472,7 +480,9 @@ fn (t &Type) find_method(name string) Fn { return method } } + //println('ret Fn{}') return Fn{} + //return none } /* diff --git a/compiler/tests/option_test.v b/compiler/tests/option_test.v index 6e3733ac9e..8342d12be7 100644 --- a/compiler/tests/option_test.v +++ b/compiler/tests/option_test.v @@ -16,9 +16,21 @@ fn err_call(ok bool) ?int { return 42 } +fn ret_none() ?int { + //return error('wtf') //none + return none +} + fn test_option_for_base_type_without_variable() { val := err_call(true) or { panic(err) } assert val == 42 + println('hm') + val2 := ret_none() or { + println('yep') + return + } + println('nice') + println(val2) } diff --git a/compiler/token.v b/compiler/token.v index 1e9a312ce0..5a1e2b5ac8 100644 --- a/compiler/token.v +++ b/compiler/token.v @@ -96,6 +96,7 @@ enum Token { key_match key_module key_mut + key_none key_return key_select key_sizeof @@ -222,6 +223,7 @@ fn build_token_str() []string { s[Token.key_defer] = 'defer' s[Token.key_match] = 'match' s[Token.key_select] = 'select' + s[Token.key_none] = 'none' return s } diff --git a/september.plan b/september.plan index 77448dfbf7..ddafbb633e 100644 --- a/september.plan +++ b/september.plan @@ -4,10 +4,13 @@ - wrap up orm - fix vorum, migrate to orm - wrap up memory management +- remove all compiler memory leaks - fix child function calls + fix non-ascii rendering in gg (ä, å, etc) - cache all tokens once - enable vfmt +- optimize the parser (reduce map lookups) +- cache vlib (right now it's re-compiled every time) - fix openssl on older linux distros - chat.vlang.io - rewrite objective c code in v (ui_mac.m) @@ -26,9 +29,6 @@ + javascript backend - new playground with a v compiler running in the browser + o(log n) type lookup -- remove all compiler memory leaks -- optimize the parser (reduce map lookups) - prebuilt binaries for all platforms -- cache vlib (right now it's re-compiled every time) - fix interfaces diff --git a/vlib/builtin/map.v b/vlib/builtin/map.v index 0720770ba1..c84a5b2832 100644 --- a/vlib/builtin/map.v +++ b/vlib/builtin/map.v @@ -155,8 +155,7 @@ fn (m map) bs(query string, start, end int, out voidptr) { fn preorder_keys(node &mapnode, keys mut []string, key_i int) int { mut i := key_i if !node.is_empty { - mut a := *keys - a[i] = node.key + keys[i] = node.key i++ } if !isnil(node.left) { diff --git a/vlib/builtin/option.v b/vlib/builtin/option.v index 42203813bf..f504b6161f 100644 --- a/vlib/builtin/option.v +++ b/vlib/builtin/option.v @@ -5,21 +5,26 @@ module builtin struct Option { - data [255]byte - error string - ok bool + data [255]byte + error string + ok bool + is_none bool } // `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }` fn opt_ok(data voidptr, size int) Option { if size >= 255 { - panic('option size too big: $size (max is 255), this is a temporary limit') - } - res := Option { + panic('option size too big: $size (max is 255), this is a temporary limit') + } + res := Option { ok: true } - C.memcpy(res.data, data, size) - return res + C.memcpy(res.data, data, size) + return res +} + +fn opt_none() Option { + return Option{ is_none: true } } pub fn error(s string) Option {