836 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			V
		
	
	
			
		
		
	
	
			836 lines
		
	
	
		
			20 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 {
 | 
						|
	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'
 | 
						|
	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`')
 | 
						|
			}
 | 
						|
			// 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' }
 | 
						|
 |