// Copyright (c) 2019-2024 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 parser import v.ast import v.token const max_expr_level = 100 @[inline] fn (mut p Parser) check_expr_level() ! { if p.expr_level > max_expr_level { return error('expr level > ${max_expr_level}') } } fn (mut p Parser) expr_no_value(precedence int) ast.Expr { old_expecting_value := p.expecting_value p.expecting_value = false defer { p.expecting_value = old_expecting_value } return p.check_expr(precedence) or { if token.is_decl(p.tok.kind) && p.disallow_declarations_in_script_mode() { return ast.empty_expr } p.unexpected(prepend_msg: 'invalid expression:') } } fn (mut p Parser) expr(precedence int) ast.Expr { old_expecting_value := p.expecting_value p.expecting_value = true defer { p.expecting_value = old_expecting_value } return p.check_expr(precedence) or { if token.is_decl(p.tok.kind) && p.disallow_declarations_in_script_mode() { return ast.empty_expr } p.unexpected(prepend_msg: 'invalid expression:') } } fn (mut p Parser) check_expr(precedence int) !ast.Expr { p.trace_parser('expr(${precedence})') p.expr_level++ defer { p.expr_level-- } p.check_expr_level()! mut node := ast.empty_expr is_stmt_ident := p.is_stmt_ident p.is_stmt_ident = false if !p.pref.is_fmt { p.eat_comments() } if p.inside_if_cond { p.if_cond_comments << p.eat_comments() } // Prefix match p.tok.kind { .key_mut, .key_shared, .key_atomic, .key_static, .key_volatile { if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { node = p.call_expr(p.language, p.mod) } else { ident := p.ident(.v) node = ident if p.peek_tok.kind != .assign && (p.inside_if_cond || p.inside_match) { p.scope.mark_var_as_used(ident.name) } p.add_defer_var(ident) p.is_stmt_ident = is_stmt_ident } } .name, .question { if p.peek_tok.kind == .name && p.tok.lit == 'sql' { node = p.sql_expr() } else if p.peek_tok.kind == .lcbr && p.tok.lit == 'map' && !(p.builtin_mod && p.file_base in ['map.v', 'map_d_gcboehm_opt.v']) { p.error_with_pos("deprecated map syntax, use syntax like `{'age': 20}`", p.tok.pos()) } else if p.tok.kind == .question && p.peek_tok.kind == .amp { node = p.prefix_expr() } else if p.inside_for_expr && p.tok.kind == .name && ((p.tok.lit[0].is_capital() && p.peek_tok.kind == .lcbr && p.peek_token(2).kind in [.rcbr, .name]) || (p.inside_array_lit && p.peek_tok.kind == .dot && p.peek_token(2).kind == .name && p.peek_token(2).lit[0].is_capital() && p.peek_token(3).kind == .lcbr && p.peek_token(4).kind in [.rcbr, .name])) { node = p.struct_init(p.mod + '.' + p.tok.lit, .normal, false) } else if p.is_generic_name() && p.peek_tok.kind == .lcbr && p.peek_token(2).kind == .rcbr && p.peek_token(2).line_nr == p.tok.line_nr { node = p.struct_init(p.mod + '.' + p.tok.lit, .normal, false) } else { if p.inside_comptime_if && p.is_generic_name() && p.peek_tok.kind != .dot { // $if T is string {} p.expecting_type = true } node = p.name_expr() p.is_stmt_ident = is_stmt_ident } } .string { node = p.string_expr() } .comment { node = p.comment() return node } .dot { // .enum_val node = p.enum_val() } .at { node = p.at() } .dollar { match p.peek_tok.kind { .key_typeof, .key_sizeof, .key_isreftype, .key_dump, .key_offsetof { start_pos := p.tok.pos() p.check(.dollar) match p.tok.kind { .key_typeof { node = p.parse_typeof_expr(start_pos) } .key_sizeof, .key_isreftype { node = p.parse_sizeof_or_isreftype_expr() } .key_dump { node = p.parse_dump_expr(start_pos) } .key_offsetof { node = p.parse_offsetof_expr(start_pos) } else {} } p.is_stmt_ident = is_stmt_ident } .name, .key_struct, .key_enum, .key_interface, .key_shared { if p.peek_tok.lit in comptime_types { node = p.parse_comptime_type() } else { node = p.comptime_call() } p.is_stmt_ident = is_stmt_ident } .key_if { mut is_expr := false if p.prev_tok.kind.is_assign() { is_expr = true } return p.if_expr(true, is_expr) } .key_match { return p.match_expr(true, p.prev_tok.kind.is_assign()) } else { return p.unexpected_with_pos(p.peek_tok.pos(), got: '`$`' ) } } } .chartoken { node = ast.CharLiteral{ val: p.tok.lit pos: p.tok.pos() } p.next() } .amp, .mul, .not, .bit_not, .arrow { // &x, *x, !x, ~x, <-x node = p.prefix_expr() } .power { node = p.power_prefix_expr() } .plus { // +1, +a node = p.prefix_expr() } .minus { // -1, -a if p.peek_tok.kind == .number && !(p.peek_token(2).kind == .power && p.peek_token(2).line_nr == p.tok.line_nr) { node = p.parse_number_literal() } else { node = p.prefix_expr() } } .key_go, .key_spawn { if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { node = p.call_expr(p.language, p.mod) } else { if (p.pref.use_coroutines || p.pref.is_fmt) && p.tok.kind == .key_go { mut go_expr := p.go_expr() go_expr.is_expr = true node = go_expr } else { mut spawn_expr := p.spawn_expr() spawn_expr.is_expr = true node = spawn_expr } } } .key_true, .key_false { if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { node = p.call_expr(p.language, p.mod) } else { node = ast.BoolLiteral{ val: p.tok.kind == .key_true pos: p.tok.pos() } p.next() } } .key_match { node = if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { p.call_expr(p.language, p.mod) } else { p.match_expr(false, false) } } .key_select { if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { node = p.call_expr(p.language, p.mod) } else { node = p.select_expr() } } .key_nil { node = ast.Nil{ pos: p.tok.pos() } p.next() } .number { node = p.parse_number_literal() } .lpar { mut pos := p.tok.pos() p.check(.lpar) mut comments := p.eat_comments() node = p.expr(0) comments << p.eat_comments() p.check(.rpar) rpar_pos := p.prev_tok.pos() p.attach_or_block_to_parenthesized_expr(mut node) node = ast.ParExpr{ expr: node pos: pos.extend(rpar_pos) comments: comments } } .key_if { if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { node = p.call_expr(p.language, p.mod) } else { mut is_expr := false if p.prev_tok.kind.is_assign() { is_expr = true } node = p.if_expr(false, is_expr) } } .key_unsafe { // unsafe { mut pos := p.tok.pos() p.next() if p.inside_unsafe { err := p.error_with_pos('already inside `unsafe` block', pos) if p.tok.kind != .eof { p.recover_until_closing_rcbr() } return err } p.inside_unsafe = true p.check(.lcbr) e := p.expr(0) p.check(.rcbr) pos.update_last_line(p.prev_tok.line_nr) node = ast.UnsafeExpr{ expr: e pos: pos } p.inside_unsafe = false } .pipe, .logical_or { if nnn := p.lambda_expr() { node = nnn } else { return error('unexpected lambda expression') } } .key_lock, .key_rlock { if p.peek_tok.kind in [.lpar, .lsbr] && p.peek_tok.is_next_to(p.tok) { node = p.call_expr(p.language, p.mod) } else { node = p.lock_expr() } } .lsbr { if p.expecting_type { // parse json.decode type (`json.decode([]User, s)`) node = p.name_expr() } else { // check is `cast_expr` or `array_init` // [name.name][]a{} => array_init // [name.name][]a(expr) => cast_expr, same line line_nr := p.tok.line_nr mut n := 1 mut prev_n_tok := p.tok mut peek_n_tok := p.peek_token(n) mut sbr_level := 1 for peek_n_tok.kind in [.name, .dot, .lsbr, .rsbr, .number] { if peek_n_tok.kind == .rsbr { sbr_level-- } else if peek_n_tok.kind == .lsbr { sbr_level++ } if peek_n_tok.kind == .dot && prev_n_tok.kind != .name { // [xxx].method() break } n++ prev_n_tok = peek_n_tok peek_n_tok = p.peek_token(n) } mut is_cast_expr := peek_n_tok.kind == .lpar && sbr_level == 0 && peek_n_tok.line_nr == line_nr // If the matching `)` is followed by `{`, this can still be an array init when // the element type itself contains parentheses, e.g. `[](?Type){}` or // `[]thread (T1, T2){}`. if is_cast_expr && (prev_n_tok.kind == .rsbr || prev_n_tok.lit == 'thread') { mut par_level := 0 for i := n; true; i++ { tk := p.peek_token(i) if tk.kind == .eof { break } if tk.kind == .lpar { par_level++ } else if tk.kind == .rpar { par_level-- if par_level == 0 { if p.peek_token(i + 1).kind == .lcbr { is_cast_expr = false } break } } } } if is_cast_expr { pos := p.tok.pos() typ := p.parse_type() typname := p.table.sym(typ).name p.check(.lpar) expr := p.expr(0) p.check(.rpar) node = ast.CastExpr{ typ: typ typname: typname expr: expr pos: pos } } else { node = p.array_init(false, ast.void_type) } } } .key_none { pos := p.tok.pos() p.next() node = ast.None{ pos: pos } } .key_typeof { node = p.parse_typeof_expr(p.tok.pos()) } .key_sizeof, .key_isreftype { node = p.parse_sizeof_or_isreftype_expr() } .key_dump { node = p.parse_dump_expr(p.tok.pos()) } .key_offsetof { node = p.parse_offsetof_expr(p.tok.pos()) } .key_likely, .key_unlikely { is_likely := p.tok.kind == .key_likely p.next() p.check(.lpar) lpos := p.tok.pos() expr := p.expr(0) p.check(.rpar) node = ast.Likely{ expr: expr pos: lpos is_likely: is_likely } } .lcbr { if p.is_sql_query_data_expr() { node = p.sql_query_data_expr() } else { // Map `{"age": 20}` p.next() node = p.map_init() p.check(.rcbr) } } .key_fn { if p.expecting_type { // Anonymous function type start_pos := p.tok.pos() return ast.TypeNode{ typ: p.parse_type() pos: start_pos.extend(p.prev_tok.pos()) } } else { // Anonymous function node = p.anon_fn() if p.file_backend_mode == .v || p.file_backend_mode == .c { p.register_auto_import('builtin.closure') } // its a call // NOTE: this could be moved to just before the pratt loop // then anything can be a call, eg. `index[2]()` or `struct.field()` // but this would take a bit of modification if p.tok.kind == .lpar { p.next() pos := p.tok.pos() args := p.call_args() p.check(.rpar) or_block := p.gen_or_block() node = ast.CallExpr{ name: 'anon' left: node args: args pos: pos or_block: or_block scope: p.scope is_return_used: p.expecting_value } } return node } } .inc, .dec { same_line_with_next := p.tok.line_nr == p.peek_tok.line_nr next_tok_name := p.peek_tok.kind == .name if next_tok_name && same_line_with_next { p.prefix_inc_dec_error() } } else { if p.tok.kind == .key_struct && p.peek_tok.kind == .lcbr { if p.expecting_type && p.inside_call_args { // Anonymous struct for json.decode tok_pos := p.tok.pos() return ast.TypeNode{ stmt: p.struct_decl(true) pos: tok_pos typ: ast.void_type } } else { // Anonymous struct if !p.is_explicit_anon_struct_init() { p.next() } return p.struct_init('', .anon, false) } } else if p.tok.kind == .key_type { // variable name: type ident := p.ident(.v) node = ident p.scope.mark_var_as_used(ident.name) p.add_defer_var(ident) p.is_stmt_ident = is_stmt_ident } else if p.tok.kind != .eof && !(p.tok.kind == .rsbr && p.inside_asm) { // eof should be handled where it happens return error('none') // return p.unexpected(prepend_msg: 'invalid expression: ') } } } if p.inside_array_lit { if p.tok.kind in [.minus, .mul, .amp, .arrow] && p.tok.pos + 1 == p.peek_tok.pos && p.prev_tok.pos + p.prev_tok.len + 1 != p.peek_tok.pos { return node } } if p.inside_if_cond { p.if_cond_comments << p.eat_comments() } if p.pref.is_fmt && p.tok.kind == .comment && p.peek_tok.kind.is_infix() && !p.inside_map_init && !(p.peek_tok.kind == .mul && p.peek_tok.pos().line_nr != p.tok.pos().line_nr) { p.left_comments = p.eat_comments() } return p.expr_with_left(node, precedence, is_stmt_ident) } fn (p &Parser) is_explicit_anon_struct_init() bool { mut n := 2 mut curlies := 1 for curlies > 0 { tok := p.peek_token(n) if tok.kind == .eof { return false } if tok.kind == .lcbr { curlies++ } else if tok.kind == .rcbr { curlies-- } n++ } return p.peek_token(n).kind == .lcbr } fn (mut p Parser) parse_typeof_expr(start_pos token.Pos) ast.Expr { p.next() // typeof if p.tok.kind == .lsbr { p.check(.lsbr) type_pos := p.tok.pos() typ := p.parse_type() p.check(.rsbr) p.check(.lpar) p.check(.rpar) return ast.TypeOf{ is_type: true typ: typ pos: type_pos.extend(p.tok.pos()) } } p.check(.lpar) expr := p.expr(0) p.check(.rpar) if p.tok.kind != .dot && p.tok.line_nr == p.prev_tok.line_nr && !p.inside_array_init_type_expr { if !p.inside_unsafe { p.warn_with_pos('use e.g. `typeof(expr).name` or `sum_type_instance.type_name()` instead', start_pos) } } return ast.TypeOf{ is_type: false expr: expr pos: start_pos.extend(p.tok.pos()) } } fn (mut p Parser) parse_sizeof_or_isreftype_expr() ast.Expr { is_reftype := p.tok.kind == .key_isreftype p.next() // sizeof or isreftype if p.tok.kind == .lsbr { // parse sizeof[T]() and isreftype[T]() without guessing: p.check(.lsbr) mut type_pos := p.tok.pos() typ := p.parse_type() type_pos = type_pos.extend(p.tok.pos()) p.check(.rsbr) p.check(.lpar) p.check(.rpar) if is_reftype { return ast.IsRefType{ is_type: true typ: typ pos: type_pos } } return ast.SizeOf{ is_type: true typ: typ pos: type_pos } } p.check(.lpar) pos := p.tok.pos() mut is_known_var := p.scope.mark_var_as_used(p.tok.lit) || p.table.global_scope.known_const(p.mod + '.' + p.tok.lit) //|| p.table.known_fn(p.mod + '.' + p.tok.lit) // assume `mod.` prefix leads to a type mut is_type := p.known_import(p.tok.lit) || p.tok.kind.is_start_of_type() || (p.tok.lit.len > 0 && p.tok.lit[0].is_capital()) if p.peek_tok.kind == .string && p.tok.lit in ['c', 'r'] { is_known_var = false is_type = false } if is_known_var || !is_type { expr := p.expr(0) if is_reftype { result := ast.IsRefType{ is_type: false expr: expr pos: pos } p.check(.rpar) return result } result := ast.SizeOf{ is_type: false expr: expr pos: pos } p.check(.rpar) return result } if p.tok.kind == .name { p.register_used_import(p.tok.lit) } save_expr_mod := p.expr_mod p.expr_mod = '' arg_type := p.parse_type() p.expr_mod = save_expr_mod p.check(.rpar) if is_reftype { return ast.IsRefType{ guessed_type: true is_type: true typ: arg_type pos: pos } } return ast.SizeOf{ guessed_type: true is_type: true typ: arg_type pos: pos } } fn (mut p Parser) parse_dump_expr(start_pos token.Pos) ast.Expr { p.next() // dump p.check(.lpar) expr := p.expr(0) if p.tok.kind == .comma && p.peek_tok.kind == .rpar { p.next() } p.check(.rpar) mut pos := p.tok.pos() pos.update_last_line(p.prev_tok.line_nr) return ast.DumpExpr{ expr: expr pos: start_pos.extend(pos) } } fn (mut p Parser) parse_offsetof_expr(start_pos token.Pos) ast.Expr { p.next() // __offsetof p.check(.lpar) st := p.parse_type() p.check(.comma) if p.tok.kind != .name { return p.unexpected(got: '`${p.tok.lit}`', additional_msg: 'expecting struct field') } field := p.tok.lit p.next() p.check(.rpar) return ast.OffsetOf{ struct_type: st field: field pos: start_pos } } fn (p &Parser) can_use_expr_as_struct_init_type(expr ast.Expr) bool { return match expr { ast.ParExpr { p.can_use_expr_as_struct_init_type(expr.expr) } ast.SelectorExpr { expr.expr is ast.TypeOf && expr.field_name in ['idx', 'typ', 'unaliased_typ', 'key_type', 'value_type', 'element_type', 'pointee_type', 'payload_type'] } else { false } } } fn (mut p Parser) expr_with_left(left ast.Expr, precedence int, is_stmt_ident bool) ast.Expr { mut node := left if p.inside_asm && p.prev_tok.pos().line_nr < p.tok.pos().line_nr { return node } p.process_custom_orm_operators() // Infix for { if p.tok.kind == .lpar && p.tok.line_nr == p.prev_tok.line_nr && node in [ast.CallExpr, ast.IndexExpr, ast.ParExpr, ast.SelectorExpr] { p.promote_if_expr_to_value(mut node) node = p.call_expr_with_left(node) p.is_stmt_ident = is_stmt_ident continue } if !p.inside_array_init_type_expr && p.tok.kind == .lcbr && p.tok.is_next_to(p.prev_tok) && p.can_use_expr_as_struct_init_type(node) { node = p.struct_init_with_type_expr(node, .normal) p.is_stmt_ident = is_stmt_ident continue } if precedence >= p.tok.kind.precedence() { return node } if p.tok.kind == .dot { // no spaces or line break before dot in map_init if (p.inside_map_init || p.inside_array_lit) && p.tok.pos - p.prev_tok.pos > p.prev_tok.len { return node } p.promote_if_expr_to_value(mut node) node = p.dot_expr(node) if p.name_error { return node } p.is_stmt_ident = is_stmt_ident } else if left !is ast.IntegerLiteral && p.tok.kind in [.lsbr, .nilsbr] && (p.tok.line_nr == p.prev_tok.line_nr || (p.prev_tok.kind == .string && p.tok.line_nr == p.prev_tok.line_nr + p.prev_tok.lit.count('\n'))) { p.promote_if_expr_to_value(mut node) if p.peek_tok.kind == .question && p.peek_token(2).kind == .name { p.next() p.error_with_pos('cannot use Option type name as concrete type', p.tok.pos()) } else if p.tok.kind == .nilsbr { node = p.index_expr(node, true) } else { node = p.index_expr(node, false) } p.is_stmt_ident = is_stmt_ident } else if p.tok.kind == .key_as && p.tok.line_nr == p.prev_tok.line_nr { // sum type as cast `x := SumType as Variant` if !p.inside_asm { pos := p.tok.pos() p.promote_if_expr_to_value(mut node) p.next() typ := p.parse_type() node = ast.AsCast{ expr: node typ: typ pos: pos } } else { return node } } else if node !is ast.CastExpr && p.tok.kind == .left_shift && p.is_stmt_ident { // arr << elem tok := p.tok mut pos := tok.pos() p.next() old_assign_rhs := p.inside_assign_rhs p.inside_assign_rhs = true right := p.expr(precedence - 1) p.inside_assign_rhs = old_assign_rhs pos.update_last_line(p.prev_tok.line_nr) if mut node is ast.IndexExpr { node.recursive_arraymap_set_is_setter() } node = ast.InfixExpr{ left: node right: right op: tok.kind pos: pos is_stmt: true } } else if p.tok.kind.is_infix() && !(p.tok.kind in [.minus, .amp, .mul, .arrow, .key_as, .key_in, .key_is] && p.tok.line_nr != p.prev_tok.line_nr) { // continue on infix expr p.promote_if_expr_to_value(mut node) node = p.infix_expr(node) // return early `if bar is SumType as b {` if p.tok.kind == .key_as && p.inside_if { return node } } else if p.tok.kind in [.inc, .dec] || (p.tok.kind == .question && p.inside_ct_if_expr) { // Postfix // detect `f(x++)`, `a[x++]` if p.peek_tok.kind in [.rpar, .rsbr] { if !p.inside_ct_if_expr { if !p.pref.translated && !p.is_translated { p.warn_with_pos('`${p.tok.kind}` operator can only be used as a statement', p.tok.pos()) } } } inc_dec_tok := p.tok.kind in [.inc, .dec] same_line_with_prev := p.tok.line_nr == p.prev_tok.line_nr same_line_with_next := p.tok.line_nr == p.peek_tok.line_nr next_tok_name := p.peek_tok.kind == .name // 1. name // 2. ++ // ^^ current token if inc_dec_tok && !same_line_with_prev && !next_tok_name { p.error_with_pos('${p.tok} must be on the same line as the previous token', p.tok.pos()) } // a++ a-- // ^^ current token // a[i]++ a-- // ^^ current token // check if op attached to previous name prev_name_or_rsbr := p.prev_tok.kind in [.name, .rsbr] // 1. ++name // ^^ current token if inc_dec_tok && same_line_with_next && next_tok_name && (!prev_name_or_rsbr || !same_line_with_prev) { p.prefix_inc_dec_error() } if mut node is ast.IndexExpr { node.recursive_mapset_is_setter(true) } is_c2v_prefix := p.peek_tok.kind == .dollar && p.peek_tok.is_next_to(p.tok) node = ast.PostfixExpr{ op: p.tok.kind expr: node pos: p.tok.pos() is_c2v_prefix: is_c2v_prefix } if is_c2v_prefix { p.next() } p.next() // return node // TODO: bring back, only allow ++/-- in exprs in translated code } else { return node } } return node } fn (mut p Parser) promote_if_expr_to_value(mut expr ast.Expr) { match mut expr { ast.IfExpr { expr.is_expr = true expr.force_expr = true } else {} } } fn unwrap_parenthesized_call_left(expr ast.Expr) ast.Expr { return match expr { ast.ParExpr { unwrap_parenthesized_call_left(expr.expr) } else { expr } } } fn (mut p Parser) call_expr_with_left(left ast.Expr) ast.CallExpr { p.next() pos := p.tok.pos() args := p.call_args() p.check(.rpar) or_block := p.gen_or_block() unwrapped_left := unwrap_parenthesized_call_left(left) mut name := '' mut name_pos := token.Pos{} mut mod := '' mut kind := ast.CallKind.unknown if unwrapped_left is ast.Ident { ident := unwrapped_left as ast.Ident mut fn_name := ident.name if p.is_imported_symbol(fn_name) { check := !p.imported_symbols_used[fn_name] fn_name = p.imported_symbols[fn_name] if check { p.register_used_import_for_symbol_name(fn_name) } } name = fn_name name_pos = ident.pos mod = p.mod kind = p.call_kind(fn_name) } return ast.CallExpr{ name: name name_pos: name_pos mod: mod kind: kind left: unwrapped_left args: args pos: pos scope: p.scope or_block: or_block is_return_used: p.expecting_value is_paren_wrapped_call: left is ast.ParExpr } } fn (mut p Parser) gen_or_block() ast.OrExpr { if p.tok.kind == .key_orelse { // `foo() or {}`` or_stmts, or_pos, or_scope := p.or_block(.with_err_var) return ast.OrExpr{ kind: ast.OrKind.block stmts: or_stmts pos: or_pos scope: or_scope } } else if p.tok.kind in [.question, .not] { or_pos := p.tok.pos() is_not := p.tok.kind == .not // `foo()?` p.next() if p.inside_defer { p.error_with_pos('error propagation not allowed inside `defer` blocks', p.prev_tok.pos()) } return ast.OrExpr{ kind: if is_not { ast.OrKind.propagate_result } else { ast.OrKind.propagate_option } scope: p.scope pos: or_pos } } else { return ast.OrExpr{ kind: ast.OrKind.absent scope: ast.empty_scope pos: p.tok.pos() } } } fn (mut p Parser) attach_or_block_to_parenthesized_expr(mut expr ast.Expr) bool { if p.tok.kind != .key_orelse { return false } match mut expr { ast.CallExpr { if expr.or_block.kind == .block { return false } or_stmts, or_pos, or_scope := p.or_block(.with_err_var) expr.or_block = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } ast.ComptimeCall { if expr.or_block.kind == .block { return false } or_stmts, or_pos, or_scope := p.or_block(.with_err_var) expr.or_block = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } ast.Ident { if expr.or_expr.kind == .block { return false } err_var_mode := if expr.or_expr.kind in [.propagate_option, .propagate_result] { OrBlockErrVarMode.with_err_var } else { OrBlockErrVarMode.no_err_var } or_stmts, or_pos, or_scope := p.or_block(err_var_mode) expr.or_expr = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } ast.IndexExpr { if expr.or_expr.kind == .block { return false } err_var_mode := if expr.or_expr.kind in [.propagate_option, .propagate_result] { OrBlockErrVarMode.with_err_var } else { OrBlockErrVarMode.no_err_var } or_stmts, or_pos, or_scope := p.or_block(err_var_mode) expr.or_expr = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } ast.InfixExpr { if expr.op != .arrow || expr.or_block.kind == .block { return false } or_stmts, or_pos, or_scope := p.or_block(.with_err_var) expr.or_block = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } ast.ParExpr { return p.attach_or_block_to_parenthesized_expr(mut expr.expr) } ast.PrefixExpr { if expr.op != .arrow || expr.or_block.kind == .block { return false } or_stmts, or_pos, or_scope := p.or_block(.with_err_var) expr.or_block = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } ast.SelectorExpr { if expr.or_block.kind == .block { return false } or_stmts, or_pos, or_scope := p.or_block(.with_err_var) expr.or_block = ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos scope: or_scope } return true } else { return false } } } fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { prev_inside_infix := p.inside_infix p.inside_infix = true defer { p.inside_infix = prev_inside_infix } op := p.tok.kind if op == .arrow { p.or_is_handled = true p.register_auto_import('sync') } else if op == .power { p.register_auto_import('math') } precedence := p.tok.kind.precedence() mut pos := p.tok.pos() p.next() if p.inside_if_cond { p.if_cond_comments << p.eat_comments() } mut before_op_comments := []ast.Comment{} if p.pref.is_fmt && p.left_comments.len > 0 { before_op_comments = p.left_comments.clone() p.left_comments = [] } p.left_comments = [] after_op_comments := p.eat_comments() mut right := ast.empty_expr prev_expecting_type := p.expecting_type if op in [.key_is, .not_is] { p.expecting_type = true } is_key_in := op in [.key_in, .not_in] if is_key_in { p.inside_in_array = true } right_op_pos := p.tok.pos() old_assign_rhs := p.inside_assign_rhs if op in [.decl_assign, .assign] { p.inside_assign_rhs = true } if p.inside_match_case && p.tok.kind == .lcbr { // In a match branch, a bare `{` opens the branch body; it is not the rhs of // the infix operator. p.unexpected(prepend_msg: 'invalid expression:') } else { right = p.expr(if op == .power { precedence - 1 } else { precedence }) } p.inside_assign_rhs = old_assign_rhs // Keep rejecting doubled operator forms like `5 - - -5`, but allow valid // infix rhs expressions such as `a == -(-2)` and `a == +2`. if op in [.plus, .minus, .mul] && mut right is ast.PrefixExpr { mut right_expr := right.right right_expr = right_expr.remove_par() if right.op == op && right_expr.is_pure_literal() { p.error_with_pos('invalid expression: unexpected token `${op}`', right_op_pos) } } if is_key_in { if p.tok.kind == .dotdot { p.check(.dotdot) pos_high := p.tok.pos() right = ast.RangeExpr{ low: right has_low: true high: p.expr(int(token.Precedence.in_as)) has_high: true pos: pos_high is_gated: false } } p.inside_in_array = false } p.expecting_type = prev_expecting_type mut or_stmts := []ast.Stmt{} mut or_kind := ast.OrKind.absent mut or_pos := p.tok.pos() mut or_scope := ast.empty_scope // allow `x := <-ch or {...}` to handle closed channel if op == .arrow { if mut right is ast.SelectorExpr { or_kind = right.or_block.kind or_stmts = right.or_block.stmts.clone() right.or_block = ast.OrExpr{} } if mut right is ast.CallExpr { or_kind = right.or_block.kind or_stmts = right.or_block.stmts.clone() or_scope = right.or_block.scope right.or_block = ast.OrExpr{} } if p.tok.kind == .key_orelse { or_kind = .block or_stmts, or_pos, or_scope = p.or_block(.with_err_var) } if p.tok.kind == .question { p.next() or_kind = .propagate_option or_scope = p.scope } p.or_is_handled = false } pos.update_last_line(p.prev_tok.line_nr) return ast.InfixExpr{ left: left right: right op: op pos: pos is_stmt: p.is_stmt_ident before_op_comments: before_op_comments after_op_comments: after_op_comments or_block: ast.OrExpr{ stmts: or_stmts kind: or_kind pos: or_pos scope: or_scope } } } fn (p &Parser) fileis(s string) bool { return p.file_path.contains(s) } fn (mut p Parser) prefix_expr() ast.Expr { mut pos := p.tok.pos() is_option := p.tok.kind == .question if is_option { p.next() } op := p.tok.kind if op == .amp { p.is_amp = true } if op == .arrow { p.or_is_handled = true p.register_auto_import('sync') } // if op == .mul && !p.inside_unsafe { // p.warn('unsafe') // } p.next() mut right := p.expr(int(token.Precedence.prefix)) p.is_amp = false if op == .amp { if mut right is ast.CastExpr { // Handle &Type(x), as well as &&Type(x) etc: p.recast_as_pointer(mut right, pos) if is_option { right.typ = right.typ.set_flag(.option) } return right } if mut right is ast.SelectorExpr { // Handle &Type(x).name : if mut right.expr is ast.CastExpr { p.recast_as_pointer(mut right.expr, pos) return right } } if mut right is ast.IndexExpr { // Handle &u64(x)[idx] : if mut right.left is ast.CastExpr { p.recast_as_pointer(mut right.left, pos) return right } } mut unwrapped_right := ast.Expr(ast.EmptyExpr{}) if mut right is ast.ParExpr { if right.expr is ast.StructInit { p.note_with_pos('unnecessary `()`, use `&${ast.Expr(right.expr)}` instead of `&(${ast.Expr(right.expr)})`', right.pos) unwrapped_right = right.expr } } if unwrapped_right !is ast.EmptyExpr { right = unwrapped_right } if mut right is ast.TypeNode { right.typ = right.typ.ref() return right } } mut or_stmts := []ast.Stmt{} mut or_kind := ast.OrKind.absent mut or_pos := p.tok.pos() mut or_scope := ast.empty_scope // allow `x := <-ch or {...}` to handle closed channel if op == .arrow { if mut right is ast.SelectorExpr { or_kind = right.or_block.kind or_stmts = right.or_block.stmts.clone() right.or_block = ast.OrExpr{} } else if p.tok.kind == .key_orelse { or_kind = .block or_stmts, or_pos, or_scope = p.or_block(.with_err_var) } else if p.tok.kind == .question { p.next() or_kind = .propagate_option or_scope = p.scope } p.or_is_handled = false } pos.update_last_line(p.prev_tok.line_nr) return ast.PrefixExpr{ op: op right: right pos: pos or_block: ast.OrExpr{ stmts: or_stmts kind: or_kind pos: or_pos scope: or_scope } } } fn (mut p Parser) power_prefix_expr() ast.Expr { pos := p.tok.pos() p.next() mut right := p.expr(int(token.Precedence.prefix)) for _ in 0 .. 2 { right = ast.PrefixExpr{ op: .mul right: right pos: pos } } return right } fn (mut p Parser) recast_as_pointer(mut cast_expr ast.CastExpr, pos token.Pos) { cast_expr.typ = cast_expr.typ.ref() cast_expr.typname = if cast_expr.typ == 0 { p.table.sym(cast_expr.typ).name } else { 'unknown type name' } cast_expr.pos = pos.extend(cast_expr.pos) } // prefix_inc_dec_error reports an error for a prefix increment or decrement. // prefix increments and decrements are not allowed in V. fn (mut p Parser) prefix_inc_dec_error() { op := if p.tok.kind == .inc { '++' } else { '--' } op_pos := p.tok.pos() p.next() expr := p.expr(0) // expression `mp["name"]` after `--` in `--mp["name"]` full_expr_pos := op_pos.extend(expr.pos()) // position of full `--mp["name"]` p.error_with_pos('prefix `${op}${expr}` is unsupported, use suffix form `${expr}${op}`', full_expr_pos) } // process_custom_orm_operators checks whether a word in infix expressions is an ORM operator. // If it is, then a new kind is assigned to it, so that the parser will process it as a keyword. // This is necessary to ensure that parts of the ORM expression do not function // outside of the ORM and are not recognized as keywords in the language. // For example, there is a `like` operator in ORM, which should be used // in expressions like `name like 'M%'`, but it should not be used in V directly. @[inline] fn (mut p Parser) process_custom_orm_operators() { if !p.inside_orm { return } is_like_operator := p.tok.kind == .name && p.tok.lit == 'like' is_ilike_operator := p.tok.kind == .name && p.tok.lit == 'ilike' if is_like_operator { p.tok = token.Token{ ...p.tok kind: .key_like } } else if is_ilike_operator { p.tok = token.Token{ ...p.tok kind: .key_ilike } } } fn (mut p Parser) lambda_expr() ?ast.LambdaExpr { // a) `f(||expr)` for a callback lambda expression with 0 arguments // b) `f(|a_1,...,a_n| expr_with_a_1_etc_till_a_n)` for a callback with several arguments if !(p.tok.kind == .logical_or || (p.peek_token(1).kind == .key_mut && p.peek_token(2).kind == .name) || (p.peek_token(1).kind == .name && p.peek_token(2).kind == .pipe) || (p.peek_token(1).kind == .name && p.peek_token(2).kind == .comma)) { return none } p.open_scope() defer { p.close_scope() } p.scope.detached_from_parent = true mut pos := p.tok.pos() mut params := []ast.Ident{} if p.tok.kind == .logical_or { p.check(.logical_or) } else { p.check(.pipe) for { if p.tok.kind == .eof { break } ident := p.ident(.v) if p.scope.known_var(ident.name) { p.error_with_pos('redefinition of parameter `${ident.name}`', ident.pos) } params << ident p.scope.register(ast.Var{ name: ident.name is_mut: p.scope_var_is_mut(ident.is_mut) is_stack_obj: true pos: ident.pos is_used: true is_arg: true }) if p.tok.kind == .pipe { p.next() break } p.check(.comma) } } pos_expr := p.tok.pos() e := p.expr(0) pos_end := p.tok.pos() return ast.LambdaExpr{ pos: pos.extend(e.pos()) pos_expr: pos_expr pos_end: pos_end params: params expr: e scope: p.scope } }