From fbd71e1539087ed411cdf421677689af7b770996 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Thu, 7 Nov 2019 00:40:37 +0300 Subject: [PATCH] expression.v --- fns.h | 12 - vlib/compiler/compile_errors.v | 3 + vlib/compiler/expression.v | 689 ++++++++++++++++++++++++++++++++ vlib/compiler/fn.v | 27 +- vlib/compiler/parser.v | 702 +-------------------------------- vlib/os/os_test.v | 31 +- 6 files changed, 726 insertions(+), 738 deletions(-) delete mode 100644 fns.h create mode 100644 vlib/compiler/expression.v diff --git a/fns.h b/fns.h deleted file mode 100644 index bfe8c47616..0000000000 --- a/fns.h +++ /dev/null @@ -1,12 +0,0 @@ -// -/* -void sigaction(); -void sigemptyset(); - -// -void* memcpy(); -void* memmove(); -void* memset(); -unsigned long strlen(const char*); -char* strerror(int); -*/ diff --git a/vlib/compiler/compile_errors.v b/vlib/compiler/compile_errors.v index aa3a89d82e..e1b39acd2e 100644 --- a/vlib/compiler/compile_errors.v +++ b/vlib/compiler/compile_errors.v @@ -280,4 +280,7 @@ const ( //make_receiver_mutable = err_used_as_value = 'used as value' + + and_or_error = 'use `()` to make the boolean expression clear\n' + +'for example: `(a && b) || c` instead of `a && b || c`' ) diff --git a/vlib/compiler/expression.v b/vlib/compiler/expression.v new file mode 100644 index 0000000000..fa765731db --- /dev/null +++ b/vlib/compiler/expression.v @@ -0,0 +1,689 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module compiler + +fn (p mut Parser) bool_expression() string { + tok := p.tok + typ := p.bterm() + mut got_and := false // to catch `a && b || c` in one expression without () + mut got_or := false + for p.tok == .and || p.tok == .logical_or { + if p.tok == .and { + got_and = true + if got_or { p.error(and_or_error) } + } + if p.tok == .logical_or { + got_or = true + if got_and { p.error(and_or_error) } + } + if p.is_sql { + if p.tok == .and { + p.gen(' and ') + } + else if p.tok == .logical_or { + p.gen(' or ') + } + } else { + p.gen(' ${p.tok.str()} ') + } + p.check_space(p.tok) + p.check_types(p.bterm(), typ) + if typ != 'bool' { + p.error('logical operators `&&` and `||` require booleans') + } + } + if typ == '' { + println('curline:') + println(p.cgen.cur_line) + println(tok.str()) + p.error('expr() returns empty type') + } + return typ +} + +fn (p mut Parser) bterm() string { + ph := p.cgen.add_placeholder() + mut typ := p.expression() + p.expected_type = typ + is_str := typ=='string' && !p.is_sql + is_ustr := typ=='ustring' + is_float := typ[0] == `f` && (typ in ['f64', 'f32']) && + !(p.cur_fn.name in ['f64_abs', 'f32_abs']) && + !(p.cur_fn.name == 'eq') + is_array := typ.contains('array_') + expr_type := typ + tok := p.tok + if tok in [.eq, .gt, .lt, .le, .ge, .ne] { + //TODO: remove when array comparing is supported + if is_array { + p.error('Array comparing is not supported yet') + } + + p.fgen(' ${p.tok.str()} ') + if (is_float || is_str || is_ustr) && !p.is_js { + p.gen(',') + } + else if p.is_sql && tok == .eq { + p.gen('=') + } + else { + p.gen(tok.str()) + } + p.next() + // `id == user.id` => `id == $1`, `user.id` + if p.is_sql { + p.sql_i++ + p.gen('$' + p.sql_i.str()) + p.cgen.start_cut() + p.check_types(p.expression(), typ) + sql_param := p.cgen.cut() + p.sql_params << sql_param + p.sql_types << typ + //println('*** sql type: $typ | param: $sql_param') + } else { + p.check_types(p.expression(), typ) + } + typ = 'bool' + if is_str && !p.is_js { //&& !p.is_sql { + p.gen(')') + match tok { + .eq { p.cgen.set_placeholder(ph, 'string_eq(') } + .ne { p.cgen.set_placeholder(ph, 'string_ne(') } + .le { p.cgen.set_placeholder(ph, 'string_le(') } + .ge { p.cgen.set_placeholder(ph, 'string_ge(') } + .gt { p.cgen.set_placeholder(ph, 'string_gt(') } + .lt { p.cgen.set_placeholder(ph, 'string_lt(') } + } + } + if is_ustr { + p.gen(')') + match tok { + .eq { p.cgen.set_placeholder(ph, 'ustring_eq(') } + .ne { p.cgen.set_placeholder(ph, 'ustring_ne(') } + .le { p.cgen.set_placeholder(ph, 'ustring_le(') } + .ge { p.cgen.set_placeholder(ph, 'ustring_ge(') } + .gt { p.cgen.set_placeholder(ph, 'ustring_gt(') } + .lt { p.cgen.set_placeholder(ph, 'ustring_lt(') } + } + } + if is_float && p.cur_fn.name != 'f32_abs' && p.cur_fn.name != 'f64_abs' { + p.gen(')') + match tok { + .eq { p.cgen.set_placeholder(ph, '${expr_type}_eq(') } + .ne { p.cgen.set_placeholder(ph, '${expr_type}_ne(') } + .le { p.cgen.set_placeholder(ph, '${expr_type}_le(') } + .ge { p.cgen.set_placeholder(ph, '${expr_type}_ge(') } + .gt { p.cgen.set_placeholder(ph, '${expr_type}_gt(') } + .lt { p.cgen.set_placeholder(ph, '${expr_type}_lt(') } + } + } + } + return typ +} + +// also called on *, &, @, . (enum) +fn (p mut Parser) name_expr() string { + p.has_immutable_field = false + p.is_const_literal = false + ph := p.cgen.add_placeholder() + // amp + ptr := p.tok == .amp + deref := p.tok == .mul + if ptr || deref { + p.next() + } + mut name := p.lit + // Raw string (`s := r'hello \n ') + if name == 'r' && p.peek() == .str { + p.string_expr() + return 'string' + } + p.fgen(name) + // known_type := p.table.known_type(name) + orig_name := name + is_c := name == 'C' && p.peek() == .dot + + if is_c { + p.check(.name) + p.check(.dot) + name = p.lit + // C struct initialization + if p.peek() == .lcbr && p.table.known_type(name) { + return p.get_struct_type(name, true, ptr) + } + // C function + if p.peek() == .lpar { + return p.get_c_func_type(name) + } + // C const (`C.GLFW_KEY_LEFT`) + p.gen(name) + p.next() + return 'int' + } + // enum value? (`color == .green`) + if p.tok == .dot { + if p.table.known_type(p.expected_type) { + p.check_enum_member_access() + // println("found enum value: $p.expected_type") + return p.expected_type + } else { + p.error("unknown enum: `$p.expected_type`") + } + } + // Variable, checked before modules, so that module shadowing is allowed: + // `gg = gg.newcontext(); gg.draw_rect(...)` + if p.known_var_check_new_var(name) { + return p.get_var_type(name, ptr, deref) + } + // Module? + if p.peek() == .dot && (name == p.mod || + p.import_table.known_alias(name)) && !is_c + { + mut mod := name + // must be aliased module + if name != p.mod && p.import_table.known_alias(name) { + p.import_table.register_used_import(name) + mod = p.import_table.resolve_alias(name) + } + p.next() + p.check(.dot) + name = p.lit + p.fgen(name) + name = prepend_mod(mod_gen_name(mod), name) + } + // Unknown name, try prepending the module name to it + // TODO perf + else if !p.table.known_type(name) && + !p.table.known_fn(name) && !p.table.known_const(name) && !is_c + { + name = p.prepend_mod(name) + } else { + //println('$name') + } + // re-check + if p.known_var_check_new_var(name) { + return p.get_var_type(name, ptr, deref) + } + + // 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) { + // cast expression: float(5), byte(0), (*int)(ptr) etc + if !is_c && ( p.peek() == .lpar || (deref && p.peek() == .rpar) ) { + if deref { + name += '*' + } + else if ptr { + name += '*' + } + p.gen('(') + mut typ := name + if typ in p.cur_fn.dispatch_of.inst.keys() { + typ = p.cur_fn.dispatch_of.inst[typ] + } + p.cast(typ) + p.gen(')') + for p.tok == .dot { + typ = p.dot(typ, ph) + } + return typ + } + // Color.green + else if p.peek() == .dot { + enum_type := p.table.find_type(name) + if enum_type.cat != .enum_ { + p.error('`$name` is not an enum') + } + p.next() + p.check(.dot) + val := p.lit + if !enum_type.has_enum_val(val) { + p.error('enum `$enum_type.name` does not have value `$val`') + } + if p.expected_type == enum_type.name { + // `if color == .red` is enough + // no need in `if color == Color.red` + p.warn('`${enum_type.name}.$val` is unnecessary, use `.$val`') + } + // println('enum val $val') + p.gen(mod_gen_name(enum_type.mod) + '__' + enum_type.name + '_' + val)// `color = main__Color_green` + p.next() + return enum_type.name + } + // normal struct init (non-C) + else if p.peek() == .lcbr { + return p.get_struct_type(name, false, ptr) + } + } + + // Constant + if p.table.known_const(name) { + return p.get_const_type(name, ptr) + } + // TODO: V script? Try os module. + // Function (not method, methods are handled in `.dot()`) + mut f := p.table.find_fn_is_script(name, p.v_script) or { + // First pass, the function can be defined later. + if p.first_pass() { + p.next() + return 'void' + } + // exhaused all options type,enum,const,mod,var,fn etc + // so show undefined error (also checks typos) + p.undefined_error(name, orig_name) return '' // panics + } + // no () after func, so func is an argument, just gen its name + // TODO verify this and handle errors + peek := p.peek() + if peek != .lpar && peek != .lt { + // Register anon fn type + fn_typ := Type { + name: f.typ_str()// 'fn (int, int) string' + mod: p.mod + func: f + } + p.table.register_type2(fn_typ) + p.gen(p.table.fn_gen_name(f)) + p.next() + return f.typ_str() //'void*' + } + // TODO bring back + if f.typ == 'void' && !p.inside_if_expr { + // p.error('`$f.name` used as value') + } + + fn_call_ph := p.cgen.add_placeholder() + // println('call to fn $f.name of type $f.typ') + // TODO replace the following dirty hacks (needs ptr access to fn table) + new_f := f + p.fn_call(mut new_f, 0, '', '') + if f.is_generic { + f2 := p.table.find_fn(f.name) or { + return '' + } + // println('after call of generic instance $new_f.name(${new_f.str_args(p.table)}) $new_f.typ') + // println(' from $f2.name(${f2.str_args(p.table)}) $f2.typ : $f2.type_inst') + } + f = new_f + + // optional function call `function() or {}`, no return assignment + is_or_else := p.tok == .key_orelse + if !p.is_var_decl && is_or_else { + f.typ = p.gen_handle_option_or_else(f.typ, '', fn_call_ph) + } + else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && + f.typ.starts_with('Option_') { + opt_type := f.typ[7..] + p.error('unhandled option type: `?$opt_type`') + } + + // dot after a function call: `get_user().age` + if p.tok == .dot { + mut typ := '' + for p.tok == .dot { + // println('dot #$dc') + typ = p.dot(f.typ, ph) + } + return typ + } + //p.log('end of name_expr') + + if f.typ.ends_with('*') { + p.is_alloc = true + } + return f.typ +} + +// returns resulting type +fn (p mut Parser) expression() string { + p.is_const_literal = true + //if p.scanner.file_path.contains('test_test') { + //println('expression() pass=$p.pass tok=') + //p.print_tok() + //} + ph := p.cgen.add_placeholder() + mut typ := p.indot_expr() + is_str := typ=='string' + is_ustr := typ=='ustring' + // `a << b` ==> `array_push(&a, b)` + if p.tok == .left_shift { + if typ.contains('array_') { + // Can't pass integer literal, because push requires a void* + // a << 7 => int tmp = 7; array_push(&a, &tmp); + // _PUSH(&a, expression(), tmp, string) + tmp := p.get_tmp() + tmp_typ := typ[6..]// skip "array_" + p.check_space(.left_shift) + // Get the value we are pushing + p.gen(', (') + // Immutable? Can we push? + if !p.expr_var.is_mut && !p.pref.translated { + p.error('`$p.expr_var.name` is immutable (can\'t <<)') + } + if p.expr_var.is_arg && p.expr_var.typ.starts_with('array_') { + p.error("for now it's not possible to append an element to "+ + 'a mutable array argument `$p.expr_var.name`') + } + if !p.expr_var.is_changed { + p.mark_var_changed(p.expr_var) + } + p.gen('/*typ = $typ tmp_typ=$tmp_typ*/') + ph_clone := p.cgen.add_placeholder() + expr_type := p.expression() + // Need to clone the string when appending it to an array? + if p.pref.autofree && typ == 'array_string' && expr_type == 'string' { + p.cgen.set_placeholder(ph_clone, 'string_clone(') + p.gen(')') + } + p.gen_array_push(ph, typ, expr_type, tmp, tmp_typ) + return 'void' + } + else { + if !is_integer_type(typ) { + p.error('cannot use shift operator on non-integer type `$typ`') + } + p.next() + p.gen(' << ') + p.check_types(p.expression(), 'integer') + return typ + } + } + if p.tok == .righ_shift { + if !is_integer_type(typ) { + p.error('cannot use shift operator on non-integer type `$typ`') + } + p.next() + p.gen(' >> ') + p.check_types(p.expression(), 'integer') + return typ + } + // + - | ^ + for p.tok in [.plus, .minus, .pipe, .amp, .xor] { + tok_op := p.tok + if typ == 'bool' { + p.error('operator ${p.tok.str()} not defined on bool ') + } + is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ) + p.check_space(p.tok) + if is_str && tok_op == .plus && !p.is_js { + p.cgen.set_placeholder(ph, 'string_add(') + p.gen(',') + } + else if is_ustr && tok_op == .plus { + p.cgen.set_placeholder(ph, 'ustring_add(') + p.gen(',') + } + // 3 + 4 + else if is_num || p.is_js { + if typ == 'void*' { + // Msvc errors on void* pointer arithmatic + // ... So cast to byte* and then do the add + p.cgen.set_placeholder(ph, '(byte*)') + } + p.gen(tok_op.str()) + } + // Vec + Vec + else { + if p.pref.translated { + p.gen(tok_op.str() + ' /*doom hack*/')// TODO hack to fix DOOM's angle_t + } + else { + p.gen(',') + } + } + if is_str && tok_op != .plus { + p.error('strings only support `+` operator') + } + expr_type := p.term() + if (tok_op in [.pipe, .amp]) && !(is_integer_type(expr_type) && + is_integer_type(typ)) { + p.error('operators `&` and `|` are defined only on integer types') + } + p.check_types(expr_type, typ) + if (is_str || is_ustr) && tok_op == .plus && !p.is_js { + p.gen(')') + } + // Make sure operators are used with correct types + if !p.pref.translated && !is_str && !is_ustr && !is_num { + T := p.table.find_type(typ) + if tok_op == .plus { + if T.has_method('+') { + p.cgen.set_placeholder(ph, typ + '_plus(') + p.gen(')') + } + else { + p.error('operator + not defined on `$typ`') + } + } + else if tok_op == .minus { + if T.has_method('-') { + p.cgen.set_placeholder(ph, '${typ}_minus(') + p.gen(')') + } + else { + p.error('operator - not defined on `$typ`') + } + } + } + } + return typ +} + +fn (p mut Parser) term() string { + line_nr := p.scanner.line_nr + //if p.fileis('fn_test') { + //println('\nterm() $line_nr') + //} + typ := p.unary() + //if p.fileis('fn_test') { + //println('2: $line_nr') + //} + // `*` on a newline? Can't be multiplication, only dereference + if p.tok == .mul && line_nr != p.scanner.line_nr { + return typ + } + for p.tok == .mul || p.tok == .div || p.tok == .mod { + tok := p.tok + is_div := tok == .div + is_mod := tok == .mod + // is_mul := tok == .mod + p.next() + p.gen(tok.str())// + ' /*op2*/ ') + p.fgen(' ' + tok.str() + ' ') + if (is_div || is_mod) && p.tok == .number && p.lit == '0' { + p.error('division or modulo by zero') + } + expr_type := p.unary() + if is_mod { + if !(is_integer_type(expr_type) && is_integer_type(typ)) { + p.error('operator `mod` requires integer types') + } + } else { + p.check_types(expr_type, typ) + } + } + return typ +} + +fn (p mut Parser) unary() string { + mut typ := '' + tok := p.tok + match tok { + .not { + p.gen('!') + p.check(.not) + // typ should be bool type + typ = p.indot_expr() + if typ != 'bool' { + p.error('operator ! requires bool type, not `$typ`') + } + } + .bit_not { + p.gen('~') + p.check(.bit_not) + typ = p.bool_expression() + } + else { + typ = p.factor() + } + } + return typ +} + +fn (p mut Parser) factor() string { + mut typ := '' + tok := p.tok + match tok { + .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 + } + .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'))) && + !(p.lit[0] == `0` && (p.lit[1] == `x` || p.lit[1] == `X`)) { + typ = 'f32' + // typ = 'f64' // TODO + } else { + v_u64 := p.lit.u64() + if u64(u32(v_u64)) < v_u64 { + typ = 'u64' + } + } + if p.expected_type != '' && !is_valid_int_const(p.lit, p.expected_type) { + p.error('constant `$p.lit` overflows `$p.expected_type`') + } + p.gen(p.lit) + p.fgen(p.lit) + } + .minus { + p.gen('-') + p.fgen('-') + p.next() + return p.factor() + // Variable + } + .key_sizeof { + p.gen('sizeof(') + p.fgen('sizeof(') + p.next() + p.check(.lpar) + mut sizeof_typ := p.get_type() + p.check(.rpar) + p.gen('$sizeof_typ)') + p.fgen('$sizeof_typ)') + return 'int' + } + .amp, .dot, .mul { + // (dot is for enum vals: `.green`) + return p.name_expr() + } + .name { + // map[string]int + if p.lit == 'map' && p.peek() == .lsbr { + return p.map_init() + } + if p.lit == 'json' && p.peek() == .dot { + if !('json' in p.table.imports) { + p.error('undefined: `json`, use `import json`') + } + p.import_table.register_used_import('json') + return p.js_decode() + } + //if p.fileis('orm_test') { + //println('ORM name: $p.lit') + //} + typ = p.name_expr() + return typ + } + /* + .key_default { + p.next() + p.next() + name := p.check_name() + if name != 'T' { + p.error('default needs T') + } + p.gen('default(T)') + p.next() + return 'T' + } + */ + .lpar { + //p.gen('(/*lpar*/') + p.gen('(') + p.check(.lpar) + typ = p.bool_expression() + // Hack. If this `)` referes to a ptr cast `(*int__)__`, it was already checked + // TODO: fix parser so that it doesn't think it's a par expression when it sees `(` in + // __(__*int)( + if !p.ptr_cast { + p.check(.rpar) + } + p.ptr_cast = false + p.gen(')') + return typ + } + .chartoken { + p.char_expr() + typ = 'byte' + return typ + } + .str { + p.string_expr() + typ = 'string' + return typ + } + .key_false { + typ = 'bool' + p.gen('0') + p.fgen('false') + } + .key_true { + typ = 'bool' + p.gen('1') + p.fgen('true') + } + .lsbr { + // `[1,2,3]` or `[]` or `[20]byte` + // TODO have to return because arrayInit does next() + // everything should do next() + return p.array_init() + } + .lcbr { + // `m := { 'one': 1 }` + if p.peek() == .str { + return p.map_init() + } + // { user | name :'new name' } + return p.assoc() + } + .key_if { + typ = p.if_st(true, 0) + return typ + } + .key_match { + typ = p.match_statement(true) + return typ + } + else { + if p.pref.is_verbose || p.pref.is_debug { + next := p.peek() + println('prev=${p.prev_tok.str()}') + println('next=${next.str()}') + } + p.error('unexpected token: `${p.tok.str()}`') + } + } + p.next()// TODO everything should next() + return typ +} + +// { user | name: 'new name' } diff --git a/vlib/compiler/fn.v b/vlib/compiler/fn.v index 5fbb6171b8..5555c73ec4 100644 --- a/vlib/compiler/fn.v +++ b/vlib/compiler/fn.v @@ -301,26 +301,25 @@ fn (p mut Parser) fn_decl() { } } // simple_name := f.name - // println('!SIMP.le=$simple_name') // user.register() => User_register() has_receiver := receiver_typ.len > 0 if receiver_typ != '' { // f.name = '${receiver_typ}_${f.name}' } // full mod function name - // os.exit ==> os__exit() + // `os.exit()` ==> `os__exit()` // if !is_c && !p.builtin_mod && receiver_typ.len == 0 { - if !is_c && receiver_typ.len == 0 && (!p.builtin_mod || (p.builtin_mod && f.name == 'init')) { + if !is_c && !has_receiver && +(!p.builtin_mod || (p.builtin_mod && f.name == 'init')) { f.name = p.prepend_mod(f.name) } if p.first_pass() && receiver_typ.len == 0 { - for { - existing_fn := p.table.find_fn(f.name) or { break } - // This existing function could be defined as C decl before (no body), then we don't need to throw an erro - if !existing_fn.is_decl { - p.error('redefinition of `$f.name`') - } - break + if existing_fn := p.table.find_fn(f.name) { + // This existing function could be defined as C decl before + // (no body), then we don't need to throw an error. + if !existing_fn.is_decl { + p.error('redefinition of `$f.name`') + } } } // Generic? @@ -355,14 +354,12 @@ fn (p mut Parser) fn_decl() { p.fgen(' ') typ = p.get_type() } - // Translated C code and .vh can have empty functions (just definitions) - is_fn_header := !is_c && !p.is_vh && - //(p.pref.translated || p.pref.is_test || p.is_vh) && - p.tok != .lcbr + // V allows empty functions (just definitions) + is_fn_header := !is_c && !p.is_vh && p.tok != .lcbr if is_fn_header { f.is_decl = true } - // { required only in normal function declarations + // `{` required only in normal function declarations if !is_c && !p.is_vh && !is_fn_header { p.fgen(' ') p.check(.lcbr) diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index 295f8b6ba8..91dda2dc7f 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -182,10 +182,10 @@ fn (p mut Parser) scan_tokens() { for { res := p.scanner.scan() p.tokens << Token{ - tok: res.tok - lit: res.lit - line_nr: p.scanner.line_nr - col: p.scanner.pos - p.scanner.last_nl_pos + tok: res.tok + lit: res.lit + line_nr: p.scanner.line_nr + col: p.scanner.pos - p.scanner.last_nl_pos } if res.tok == .eof { break @@ -1449,347 +1449,6 @@ fn (p mut Parser) var_decl() { p.is_empty_c_struct_init = false } -const ( - and_or_error = 'use `()` to make the boolean expression clear\n' + -'for example: `(a && b) || c` instead of `a && b || c`' -) - -fn (p mut Parser) bool_expression() string { - tok := p.tok - typ := p.bterm() - mut got_and := false // to catch `a && b || c` in one expression without () - mut got_or := false - for p.tok == .and || p.tok == .logical_or { - if p.tok == .and { - got_and = true - if got_or { p.error(and_or_error) } - } - if p.tok == .logical_or { - got_or = true - if got_and { p.error(and_or_error) } - } - if p.is_sql { - if p.tok == .and { - p.gen(' and ') - } - else if p.tok == .logical_or { - p.gen(' or ') - } - } else { - p.gen(' ${p.tok.str()} ') - } - p.check_space(p.tok) - p.check_types(p.bterm(), typ) - if typ != 'bool' { - p.error('logical operators `&&` and `||` require booleans') - } - } - if typ == '' { - println('curline:') - println(p.cgen.cur_line) - println(tok.str()) - p.error('expr() returns empty type') - } - return typ -} - -fn (p mut Parser) bterm() string { - ph := p.cgen.add_placeholder() - mut typ := p.expression() - p.expected_type = typ - is_str := typ=='string' && !p.is_sql - is_ustr := typ=='ustring' - is_float := typ[0] == `f` && (typ in ['f64', 'f32']) && - !(p.cur_fn.name in ['f64_abs', 'f32_abs']) && - !(p.cur_fn.name == 'eq') - is_array := typ.contains('array_') - expr_type := typ - tok := p.tok - if tok in [.eq, .gt, .lt, .le, .ge, .ne] { - //TODO: remove when array comparing is supported - if is_array { - p.error('Array comparing is not supported yet') - } - - p.fgen(' ${p.tok.str()} ') - if (is_float || is_str || is_ustr) && !p.is_js { - p.gen(',') - } - else if p.is_sql && tok == .eq { - p.gen('=') - } - else { - p.gen(tok.str()) - } - p.next() - // `id == user.id` => `id == $1`, `user.id` - if p.is_sql { - p.sql_i++ - p.gen('$' + p.sql_i.str()) - p.cgen.start_cut() - p.check_types(p.expression(), typ) - sql_param := p.cgen.cut() - p.sql_params << sql_param - p.sql_types << typ - //println('*** sql type: $typ | param: $sql_param') - } else { - p.check_types(p.expression(), typ) - } - typ = 'bool' - if is_str && !p.is_js { //&& !p.is_sql { - p.gen(')') - match tok { - .eq { p.cgen.set_placeholder(ph, 'string_eq(') } - .ne { p.cgen.set_placeholder(ph, 'string_ne(') } - .le { p.cgen.set_placeholder(ph, 'string_le(') } - .ge { p.cgen.set_placeholder(ph, 'string_ge(') } - .gt { p.cgen.set_placeholder(ph, 'string_gt(') } - .lt { p.cgen.set_placeholder(ph, 'string_lt(') } - } - } - if is_ustr { - p.gen(')') - match tok { - .eq { p.cgen.set_placeholder(ph, 'ustring_eq(') } - .ne { p.cgen.set_placeholder(ph, 'ustring_ne(') } - .le { p.cgen.set_placeholder(ph, 'ustring_le(') } - .ge { p.cgen.set_placeholder(ph, 'ustring_ge(') } - .gt { p.cgen.set_placeholder(ph, 'ustring_gt(') } - .lt { p.cgen.set_placeholder(ph, 'ustring_lt(') } - } - } - if is_float && p.cur_fn.name != 'f32_abs' && p.cur_fn.name != 'f64_abs' { - p.gen(')') - match tok { - .eq { p.cgen.set_placeholder(ph, '${expr_type}_eq(') } - .ne { p.cgen.set_placeholder(ph, '${expr_type}_ne(') } - .le { p.cgen.set_placeholder(ph, '${expr_type}_le(') } - .ge { p.cgen.set_placeholder(ph, '${expr_type}_ge(') } - .gt { p.cgen.set_placeholder(ph, '${expr_type}_gt(') } - .lt { p.cgen.set_placeholder(ph, '${expr_type}_lt(') } - } - } - } - return typ -} - -// also called on *, &, @, . (enum) -fn (p mut Parser) name_expr() string { -//println('n') - p.has_immutable_field = false - p.is_const_literal = false - ph := p.cgen.add_placeholder() - // amp - ptr := p.tok == .amp - deref := p.tok == .mul - if ptr || deref { - p.next() - } - mut name := p.lit - // Raw string (`s := r'hello \n ') - if name == 'r' && p.peek() == .str { - p.string_expr() - return 'string' - } - p.fgen(name) - // known_type := p.table.known_type(name) - orig_name := name - is_c := name == 'C' && p.peek() == .dot - - if is_c { - p.check(.name) - p.check(.dot) - name = p.lit - // C struct initialization - if p.peek() == .lcbr && p.table.known_type(name) { - return p.get_struct_type(name, true, ptr) - } - // C function - if p.peek() == .lpar { - return p.get_c_func_type(name) - } - // C const (`C.GLFW_KEY_LEFT`) - p.gen(name) - p.next() - return 'int' - } - - // enum value? (`color == .green`) - if p.tok == .dot { - if p.table.known_type(p.expected_type) { - p.check_enum_member_access() - // println("found enum value: $p.expected_type") - return p.expected_type - } else { - p.error("unknown enum: `$p.expected_type`") - } - } - - // Variable, checked before modules, so module shadowing is allowed. - // (`gg = gg.newcontext(); gg.draw_rect(...)`) - if p.known_var_check_new_var(name) { - rtyp := p.get_var_type(name, ptr, deref) - return rtyp - } - - // Module? - if p.peek() == .dot && ((name == p.mod && p.table.known_mod(name)) || - p.import_table.known_alias(name)) && !is_c { - mut mod := name - // must be aliased module - if name != p.mod && p.import_table.known_alias(name) { - p.import_table.register_used_import(name) - mod = p.import_table.resolve_alias(name) - } - p.next() - p.check(.dot) - name = p.lit - p.fgen(name) - name = prepend_mod(mod_gen_name(mod), name) - } - - // Unknown name, try prepending the module name to it - // TODO perf - else if !p.table.known_type(name) && - !p.table.known_fn(name) && !p.table.known_const(name) && !is_c - { - name = p.prepend_mod(name) - } - - // re-check - if p.known_var_check_new_var(name) { - return p.get_var_type(name, ptr, deref) - } - - // 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) { - // cast expression: float(5), byte(0), (*int)(ptr) etc - if !is_c && ( p.peek() == .lpar || (deref && p.peek() == .rpar) ) { - if deref { - name += '*' - } - else if ptr { - name += '*' - } - p.gen('(') - mut typ := name - if typ in p.cur_fn.dispatch_of.inst.keys() { - typ = p.cur_fn.dispatch_of.inst[typ] - } - p.cast(typ) - p.gen(')') - for p.tok == .dot { - typ = p.dot(typ, ph) - } - return typ - } - // Color.green - else if p.peek() == .dot { - enum_type := p.table.find_type(name) - if enum_type.cat != .enum_ { - p.error('`$name` is not an enum') - } - p.next() - p.check(.dot) - val := p.lit - if !enum_type.has_enum_val(val) { - p.error('enum `$enum_type.name` does not have value `$val`') - } - if p.expected_type == enum_type.name { - // `if color == .red` is enough - // no need in `if color == Color.red` - p.warn('`${enum_type.name}.$val` is unnecessary, use `.$val`') - } - // println('enum val $val') - p.gen(mod_gen_name(enum_type.mod) + '__' + enum_type.name + '_' + val)// `color = main__Color_green` - p.next() - return enum_type.name - } - // normal struct init (non-C) - else if p.peek() == .lcbr { - return p.get_struct_type(name, false, ptr) - } - } - - // Constant - if p.table.known_const(name) { - return p.get_const_type(name, ptr) - } - // TODO: V script? Try os module. - // Function (not method, methods are handled in `.dot()`) - mut f := p.table.find_fn_is_script(name, p.v_script) or { - // First pass, the function can be defined later. - if p.first_pass() { - p.next() - return 'void' - } - // exhaused all options type,enum,const,mod,var,fn etc - // so show undefined error (also checks typos) - p.undefined_error(name, orig_name) return '' // panics - } - // no () after func, so func is an argument, just gen its name - // TODO verify this and handle errors - peek := p.peek() - if peek != .lpar && peek != .lt { - // Register anon fn type - fn_typ := Type { - name: f.typ_str()// 'fn (int, int) string' - mod: p.mod - func: f - } - p.table.register_type2(fn_typ) - p.gen(p.table.fn_gen_name(f)) - p.next() - return f.typ_str() //'void*' - } - // TODO bring back - if f.typ == 'void' && !p.inside_if_expr { - // p.error('`$f.name` used as value') - } - - fn_call_ph := p.cgen.add_placeholder() - // println('call to fn $f.name of type $f.typ') - // TODO replace the following dirty hacks (needs ptr access to fn table) - new_f := f - p.fn_call(mut new_f, 0, '', '') - if f.is_generic { - f2 := p.table.find_fn(f.name) or { - return '' - } - // println('after call of generic instance $new_f.name(${new_f.str_args(p.table)}) $new_f.typ') - // println(' from $f2.name(${f2.str_args(p.table)}) $f2.typ : $f2.type_inst') - } - f = new_f - - // optional function call `function() or {}`, no return assignment - is_or_else := p.tok == .key_orelse - if !p.is_var_decl && is_or_else { - f.typ = p.gen_handle_option_or_else(f.typ, '', fn_call_ph) - } - else if !p.is_var_decl && !is_or_else && !p.inside_return_expr && - f.typ.starts_with('Option_') { - opt_type := f.typ[7..] - p.error('unhandled option type: `?$opt_type`') - } - - // dot after a function call: `get_user().age` - if p.tok == .dot { - mut typ := '' - for p.tok == .dot { - // println('dot #$dc') - typ = p.dot(f.typ, ph) - } - return typ - } - //p.log('end of name_expr') - - if f.typ.ends_with('*') { - p.is_alloc = true - } - return f.typ -} - fn (p mut Parser) get_struct_type(name_ string, is_c bool, is_ptr bool) string { mut name := name_ if is_ptr { @@ -1898,7 +1557,7 @@ fn (p mut Parser) undefined_error(name string, orig_name string) { } else if orig_name in reserved_type_param_names { p.error('the letter `$orig_name` is reserved for type parameters') } - p.error('undefined: `$orig_name`') + p.error('undefined symbol: `$orig_name`') } fn (p mut Parser) var_expr(v Var) string { @@ -2386,357 +2045,6 @@ fn (p mut Parser) indot_expr() string { return typ } -// returns resulting type -fn (p mut Parser) expression() string { - p.is_const_literal = true - //if p.scanner.file_path.contains('test_test') { - //println('expression() pass=$p.pass tok=') - //p.print_tok() - //} - ph := p.cgen.add_placeholder() - mut typ := p.indot_expr() - is_str := typ=='string' - is_ustr := typ=='ustring' - // `a << b` ==> `array_push(&a, b)` - if p.tok == .left_shift { - if typ.contains('array_') { - // Can't pass integer literal, because push requires a void* - // a << 7 => int tmp = 7; array_push(&a, &tmp); - // _PUSH(&a, expression(), tmp, string) - tmp := p.get_tmp() - tmp_typ := typ[6..]// skip "array_" - p.check_space(.left_shift) - // Get the value we are pushing - p.gen(', (') - // Immutable? Can we push? - if !p.expr_var.is_mut && !p.pref.translated { - p.error('`$p.expr_var.name` is immutable (can\'t <<)') - } - if p.expr_var.is_arg && p.expr_var.typ.starts_with('array_') { - p.error("for now it's not possible to append an element to "+ - 'a mutable array argument `$p.expr_var.name`') - } - if !p.expr_var.is_changed { - p.mark_var_changed(p.expr_var) - } - p.gen('/*typ = $typ tmp_typ=$tmp_typ*/') - ph_clone := p.cgen.add_placeholder() - expr_type := p.expression() - // Need to clone the string when appending it to an array? - if p.pref.autofree && typ == 'array_string' && expr_type == 'string' { - p.cgen.set_placeholder(ph_clone, 'string_clone(') - p.gen(')') - } - p.gen_array_push(ph, typ, expr_type, tmp, tmp_typ) - return 'void' - } - else { - if !is_integer_type(typ) { - p.error('cannot use shift operator on non-integer type `$typ`') - } - p.next() - p.gen(' << ') - p.check_types(p.expression(), 'integer') - return typ - } - } - if p.tok == .righ_shift { - if !is_integer_type(typ) { - p.error('cannot use shift operator on non-integer type `$typ`') - } - p.next() - p.gen(' >> ') - p.check_types(p.expression(), 'integer') - return typ - } - // + - | ^ - for p.tok in [.plus, .minus, .pipe, .amp, .xor] { - tok_op := p.tok - if typ == 'bool' { - p.error('operator ${p.tok.str()} not defined on bool ') - } - is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ) - p.check_space(p.tok) - if is_str && tok_op == .plus && !p.is_js { - p.cgen.set_placeholder(ph, 'string_add(') - p.gen(',') - } - else if is_ustr && tok_op == .plus { - p.cgen.set_placeholder(ph, 'ustring_add(') - p.gen(',') - } - // 3 + 4 - else if is_num || p.is_js { - if typ == 'void*' { - // Msvc errors on void* pointer arithmatic - // ... So cast to byte* and then do the add - p.cgen.set_placeholder(ph, '(byte*)') - } - p.gen(tok_op.str()) - } - // Vec + Vec - else { - if p.pref.translated { - p.gen(tok_op.str() + ' /*doom hack*/')// TODO hack to fix DOOM's angle_t - } - else { - p.gen(',') - } - } - if is_str && tok_op != .plus { - p.error('strings only support `+` operator') - } - expr_type := p.term() - if (tok_op in [.pipe, .amp]) && !(is_integer_type(expr_type) && - is_integer_type(typ)) { - p.error('operators `&` and `|` are defined only on integer types') - } - p.check_types(expr_type, typ) - if (is_str || is_ustr) && tok_op == .plus && !p.is_js { - p.gen(')') - } - // Make sure operators are used with correct types - if !p.pref.translated && !is_str && !is_ustr && !is_num { - T := p.table.find_type(typ) - if tok_op == .plus { - if T.has_method('+') { - p.cgen.set_placeholder(ph, typ + '_plus(') - p.gen(')') - } - else { - p.error('operator + not defined on `$typ`') - } - } - else if tok_op == .minus { - if T.has_method('-') { - p.cgen.set_placeholder(ph, '${typ}_minus(') - p.gen(')') - } - else { - p.error('operator - not defined on `$typ`') - } - } - } - } - return typ -} - -fn (p mut Parser) term() string { - line_nr := p.scanner.line_nr - //if p.fileis('fn_test') { - //println('\nterm() $line_nr') - //} - typ := p.unary() - //if p.fileis('fn_test') { - //println('2: $line_nr') - //} - // `*` on a newline? Can't be multiplication, only dereference - if p.tok == .mul && line_nr != p.scanner.line_nr { - return typ - } - for p.tok == .mul || p.tok == .div || p.tok == .mod { - tok := p.tok - is_div := tok == .div - is_mod := tok == .mod - // is_mul := tok == .mod - p.next() - p.gen(tok.str())// + ' /*op2*/ ') - p.fgen(' ' + tok.str() + ' ') - if (is_div || is_mod) && p.tok == .number && p.lit == '0' { - p.error('division or modulo by zero') - } - expr_type := p.unary() - if is_mod { - if !(is_integer_type(expr_type) && is_integer_type(typ)) { - p.error('operator `mod` requires integer types') - } - } else { - p.check_types(expr_type, typ) - } - } - return typ -} - -fn (p mut Parser) unary() string { - mut typ := '' - tok := p.tok - match tok { - .not { - p.gen('!') - p.check(.not) - // typ should be bool type - typ = p.indot_expr() - if typ != 'bool' { - p.error('operator ! requires bool type, not `$typ`') - } - } - .bit_not { - p.gen('~') - p.check(.bit_not) - typ = p.bool_expression() - } - else { - typ = p.factor() - } - } - return typ -} - -fn (p mut Parser) factor() string { - mut typ := '' - tok := p.tok - match tok { - .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 - } - .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'))) && - !(p.lit[0] == `0` && (p.lit[1] == `x` || p.lit[1] == `X`)) { - typ = 'f32' - // typ = 'f64' // TODO - } else { - v_u64 := p.lit.u64() - if u64(u32(v_u64)) < v_u64 { - typ = 'u64' - } - } - if p.expected_type != '' && !is_valid_int_const(p.lit, p.expected_type) { - p.error('constant `$p.lit` overflows `$p.expected_type`') - } - p.gen(p.lit) - p.fgen(p.lit) - } - .minus { - p.gen('-') - p.fgen('-') - p.next() - return p.factor() - // Variable - } - .key_sizeof { - p.gen('sizeof(') - p.fgen('sizeof(') - p.next() - p.check(.lpar) - mut sizeof_typ := p.get_type() - p.check(.rpar) - p.gen('$sizeof_typ)') - p.fgen('$sizeof_typ)') - return 'int' - } - .amp, .dot, .mul { - // (dot is for enum vals: `.green`) - return p.name_expr() - } - .name { - // map[string]int - if p.lit == 'map' && p.peek() == .lsbr { - return p.map_init() - } - if p.lit == 'json' && p.peek() == .dot { - if !('json' in p.table.imports) { - p.error('undefined: `json`, use `import json`') - } - p.import_table.register_used_import('json') - return p.js_decode() - } - //if p.fileis('orm_test') { - //println('ORM name: $p.lit') - //} - typ = p.name_expr() - return typ - } - /* - .key_default { - p.next() - p.next() - name := p.check_name() - if name != 'T' { - p.error('default needs T') - } - p.gen('default(T)') - p.next() - return 'T' - } - */ - .lpar { - //p.gen('(/*lpar*/') - p.gen('(') - p.check(.lpar) - typ = p.bool_expression() - // Hack. If this `)` referes to a ptr cast `(*int__)__`, it was already checked - // TODO: fix parser so that it doesn't think it's a par expression when it sees `(` in - // __(__*int)( - if !p.ptr_cast { - p.check(.rpar) - } - p.ptr_cast = false - p.gen(')') - return typ - } - .chartoken { - p.char_expr() - typ = 'byte' - return typ - } - .str { - p.string_expr() - typ = 'string' - return typ - } - .key_false { - typ = 'bool' - p.gen('0') - p.fgen('false') - } - .key_true { - typ = 'bool' - p.gen('1') - p.fgen('true') - } - .lsbr { - // `[1,2,3]` or `[]` or `[20]byte` - // TODO have to return because arrayInit does next() - // everything should do next() - return p.array_init() - } - .lcbr { - // `m := { 'one': 1 }` - if p.peek() == .str { - return p.map_init() - } - // { user | name :'new name' } - return p.assoc() - } - .key_if { - typ = p.if_st(true, 0) - return typ - } - .key_match { - typ = p.match_statement(true) - return typ - } - else { - if p.pref.is_verbose || p.pref.is_debug { - next := p.peek() - println('prev=${p.prev_tok.str()}') - println('next=${next.str()}') - } - p.error('unexpected token: `${p.tok.str()}`') - } - } - p.next()// TODO everything should next() - return typ -} - -// { user | name: 'new name' } fn (p mut Parser) assoc() string { // println('assoc()') p.next() diff --git a/vlib/os/os_test.v b/vlib/os/os_test.v index 5c8f5c4e99..a451db0ed4 100644 --- a/vlib/os/os_test.v +++ b/vlib/os/os_test.v @@ -3,11 +3,11 @@ import os fn test_setenv() { os.setenv('foo', 'bar', true) assert os.getenv('foo') == 'bar' - + // `setenv` should not set if `overwrite` is false os.setenv('foo', 'bar2', false) assert os.getenv('foo') == 'bar' - + // `setenv` should overwrite if `overwrite` is true os.setenv('foo', 'bar2', true) assert os.getenv('foo') == 'bar2' @@ -24,7 +24,7 @@ fn test_write_and_read_string_to_file() { hello := 'hello world!' os.write_file(filename, hello) assert hello.len == os.file_size(filename) - + read_hello := os.read_file(filename) or { panic('error reading file $filename') } @@ -85,12 +85,12 @@ fn test_create_and_delete_folder() { fn test_dir() { $if windows { - assert os.dir('C:\\a\\b\\c') == 'C:\\a\\b' - - } $else { - assert os.dir('/var/tmp/foo') == '/var/tmp' - } -} + assert os.dir('C:\\a\\b\\c') == 'C:\\a\\b' + + } $else { + assert os.dir('/var/tmp/foo') == '/var/tmp' + } +} fn walk_callback(file string) { if file == '.' || file == '..' { @@ -102,11 +102,11 @@ fn walk_callback(file string) { fn test_walk() { folder := 'test_walk' os.mkdir(folder) - + file1 := folder+os.path_separator+'test1' - + os.write_file(file1,'test-1') - + os.walk(folder, walk_callback) os.rm(file1) @@ -117,14 +117,14 @@ fn test_cp() { $if windows { old_file_name := './example.txt' new_file_name := './new_example.txt' - + os.write_file(old_file_name, 'Test data 1 2 3, V is awesome #$%^[]!~⭐') result := os.cp(old_file_name, new_file_name) or { panic('$err: errcode: $errcode') } old_file := os.read_file(old_file_name) or { panic(err) } new_file := os.read_file(new_file_name) or { panic(err) } assert old_file == new_file - + os.rm(old_file_name) os.rm(new_file_name) } @@ -132,6 +132,8 @@ fn test_cp() { fn test_cp_r() { //fileX -> dir/fileX + // TODO clean up the files + /* os.write_file('ex1.txt', 'wow!') os.mkdir('ex') os.cp_r('ex1.txt', 'ex', false) or { panic(err) } @@ -146,6 +148,7 @@ fn test_cp_r() { assert old2 == new2 //recurring on dir -> local dir os.cp_r('ex', './', true) or { panic(err) } + */ } //fn test_fork() {