expression.v
parent
2d3944250f
commit
fbd71e1539
12
fns.h
12
fns.h
|
@ -1,12 +0,0 @@
|
|||
// <signal.h>
|
||||
/*
|
||||
void sigaction();
|
||||
void sigemptyset();
|
||||
|
||||
// <string.h>
|
||||
void* memcpy();
|
||||
void* memmove();
|
||||
void* memset();
|
||||
unsigned long strlen(const char*);
|
||||
char* strerror(int);
|
||||
*/
|
|
@ -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`'
|
||||
)
|
||||
|
|
|
@ -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' }
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue