1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00
v/vlib/compiler/expression.v
2019-12-24 16:04:49 +03:00

872 lines
21 KiB
V

// 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 {
start_ph := p.cgen.add_placeholder()
expected := p.expected_type
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')
}
if expected != typ && expected in p.table.sum_types { // TODO perf
p.cgen.set_placeholder(start_ph,
//'/*SUM TYPE CAST*/($expected) { .obj = &($typ[]) { ')
'/*SUM TYPE CAST*/($expected) { .obj = memdup(& ')
tt := typ.all_after('_') // TODO
//p.gen('}, .typ = SumType_${tt} }')//${val}_type }')
p.gen(', sizeof($typ) ), .typ = SumType_${tt} }')//${val}_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'
base := p.base_type(typ)
is_float := base[0] == `f` && (base in ['f64', 'f32']) && !(p.cur_fn.name in ['f64_abs', 'f32_abs']) && p.cur_fn.name != 'eq'
is_array := typ.starts_with('array_')
expr_type := base
tok := p.tok
/*
if tok == .assign {
p.error('no = ')
}
*/
if tok in [.eq, .gt, .lt, .le, .ge, .ne] {
// TODO: remove when array comparing is supported
if is_array {
p.error('array comparison is not supported yet')
}
p.fspace()
// 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()
p.fspace()
// `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(')
}
else {
}}
}
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(')
}
else {
}}
}
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(')
}
else {
}}
}
}
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
mut mul_nr := 0
mut deref_nr := 0
for {
if p.tok == .amp {
mul_nr++
}
else if p.tok == .mul {
deref_nr++
}
else {
break
}
p.next()
}
if p.tok == .lpar {
p.gen('*'.repeat(deref_nr))
p.gen('(')
p.check(.lpar)
mut temp_type := p.bool_expression()
p.gen(')')
p.check(.rpar)
for _ in 0 .. deref_nr {
temp_type = temp_type.replace_once('*', '')
}
return temp_type
}
mut name := p.lit
// blank identifier (not var)
if name == '_' {
p.error('cannot use `_` as value')
}
// generic type check
if name in p.generic_dispatch.inst.keys() {
name = p.generic_dispatch.inst[name]
}
// Raw string (`s := r'hello \n ')
if name == 'r' && p.peek() == .str && p.prev_tok != .str_dollar {
p.string_expr()
return 'string'
}
// C string (a zero terminated one) C.func( c'hello' )
if name == 'c' && p.peek() == .str && p.prev_tok != .str_dollar {
p.string_expr()
return 'charptr'
}
// 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.expected_type == '' {
// not an expression
if !p.table.known_type(name) {
p.error('unknown C type `$name`, ' + 'define it with `struct C.$name { ... }`')
}
return p.get_struct_type(name, true, ptr)
}
if ptr && p.peek() == .lpar {
peek2 := p.tokens[p.token_idx + 1]
// `&C.Foo(0)` cast (replacing old `&C.Foo{!}`)
if peek2.tok == .number && peek2.lit == '0' {
p.cgen.insert_before('struct /*C.Foo(0)*/ ')
p.gen('0')
p.next()
p.next()
p.next()
p.next()
return name + '*'
}
// `&C.Foo(foo)` cast
p.cast(name + '*')
return name + '*'
}
// 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_nr)
}
// 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
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_nr)
}
// 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 p.peek() == .lpar || (deref && p.peek() == .rpar) {
if deref {
name += '*'.repeat(deref_nr)
}
else if ptr {
name += '*'.repeat(mul_nr)
}
// p.gen('(')
mut typ := name
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`')
}
// `expr := Expr.BoolExpr(true)` =>
// `Expr expr = { .obj = true, .typ = BoolExpr_type };`
if val[0].is_capital() {
p.next()
p.check(.lpar)
//println('sum type $val name=$val')
// Find a corresponding tuple variant
// TODO slow, but this will be re-written anyway
mut idx := 0
for i, val_ in enum_type.enum_vals {
//println('f $field.name')
if val_ == val {
idx = i
}
}
q := p.table.tuple_variants[enum_type.name]
//println(q)
//println(q[idx])
arg_type := q[idx]
p.gen('($enum_type.name) { .obj = ($arg_type[]) { ')
p.bool_expression()
p.check(.rpar)
p.gen('}, .typ = ${val}_type }')
return enum_type.name
}
// 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 || p.peek() == .lt {
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_type(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 {
_ = 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.tok == .question {
// `files := os.ls('.')?`
return p.gen_handle_question_suffix(f, fn_call_ph)
}
else 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..].replace('ptr_', '&')
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()
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 := parse_pointer(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) {
t := p.table.find_type(typ)
if t.cat != .enum_ {
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) {
t := p.table.find_type(typ)
if t.cat != .enum_ {
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.contains('*') || is_number_type(typ) || is_number_type(p.base_type(typ))
p.check_space(p.tok)
if is_str && tok_op == .plus && !p.is_js {
p.is_alloc = true
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*)')
}
else if typ.contains('*') {
p.cgen.set_placeholder(ph, '($typ)')
}
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()
open := tok_op == .amp && p.tok in [.eq, .ne] // force precedence `(a & b) == c` //false
if tok_op in [.pipe, .amp, .xor] {
if !(is_integer_type(expr_type) && is_integer_type(typ)) {
p.error('operator ${tok_op.str()} is defined only on integer types')
}
// open = true
}
if open {
p.cgen.set_placeholder(ph, '(')
}
p.check_types(expr_type, typ)
if (is_str || is_ustr) && tok_op == .plus && !p.is_js {
p.gen(')')
}
if open {
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 {
p.handle_operator('+', typ, 'op_plus', ph, T)
}
else if tok_op == .minus {
p.handle_operator('-', typ, 'op_minus', ph, T)
}
}
}
return typ
}
fn (p mut Parser) handle_operator(op string, typ string, cpostfix string, ph int, T &Type) {
if T.has_method(op) {
p.cgen.set_placeholder(ph, '${typ}_${cpostfix}(')
p.gen(')')
}
else {
p.error('operator $op not defined on `$typ`')
}
}
fn (p mut Parser) term() string {
line_nr := p.scanner.line_nr
// if p.fileis('fn_test') {
// println('\nterm() $line_nr')
// }
ph := p.cgen.add_placeholder()
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_mul := tok == .mul
is_div := tok == .div
is_mod := tok == .mod
p.fspace()
p.next()
p.gen(tok.str()) // + ' /*op2*/ ')
oph := p.cgen.add_placeholder()
p.fspace()
if (is_div || is_mod) && p.tok == .number && p.lit == '0' {
p.error('division or modulo by zero')
}
expr_type := p.unary()
if (is_mul || is_div) && expr_type == 'string' {
p.error('operator ${tok.str()} cannot be used on strings')
}
if !is_primitive_type(expr_type) && expr_type == typ {
p.check_types(expr_type, typ)
T := p.table.find_type(typ)
// NB: oph is a char index just after the OP
before_oph := p.cgen.cur_line[..oph - 1]
after_oph := p.cgen.cur_line[oph..]
p.cgen.cur_line = before_oph + ',' + after_oph
match tok {
.mul {
p.handle_operator('*', typ, 'op_mul', ph, T)
}
.div {
p.handle_operator('/', typ, 'op_div', ph, T)
}
.mod {
p.handle_operator('%', typ, 'op_mod', ph, T)
}
else {
}}
continue
}
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)
}
.minus {
p.gen('-')
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'
}
.key_offsetof {
p.next()
p.check(.lpar)
offsetof_typ := p.get_type()
p.check(.comma)
member := p.check_name()
p.check(.rpar)
p.gen('__offsetof($offsetof_typ, $member)')
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')
}
.key_true {
typ = 'bool'
p.gen('1')
}
.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_statement(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' }