// Copyright (c) 2019-2021 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.scanner import v.ast import v.token import v.pref import v.util import v.vet import v.errors import os import hash.fnv1a pub const builtin_functions = ['print', 'println', 'eprint', 'eprintln', 'isnil', 'panic', 'exit'] pub struct Parser { pref &pref.Preferences mut: file_base string // "hello.v" file_name string // "/home/user/hello.v" file_name_dir string // "/home/user" unique_prefix string // a hash of p.file_name, used for making anon fn generation unique file_backend_mode ast.Language // .c for .c.v|.c.vv|.c.vsh files; .js for .js.v files, .amd64/.rv32/other arches for .amd64.v/.rv32.v/etc. files, .v otherwise. scanner &scanner.Scanner comments_mode scanner.CommentsMode = .skip_comments // see comment in parse_file tok token.Token prev_tok token.Token peek_tok token.Token table &ast.Table language ast.Language fn_language ast.Language // .c for `fn C.abcd()` declarations expr_level int // prevent too deep recursions for pathological programs inside_vlib_file bool // true for all vlib/ files inside_test_file bool // when inside _test.v or _test.vv file inside_if bool inside_if_expr bool inside_ct_if_expr bool inside_or_expr bool inside_for bool inside_fn bool // true even with implicit main inside_unsafe_fn bool inside_str_interp bool inside_array_lit bool inside_in_array bool inside_match bool // to separate `match A { }` from `Struct{}` inside_select bool // to allow `ch <- Struct{} {` inside `select` inside_match_case bool // to separate `match_expr { }` from `Struct{}` inside_match_body bool // to fix eval not used TODO inside_unsafe bool inside_sum_type bool // to prevent parsing inline sum type again inside_asm_template bool inside_asm bool inside_defer bool inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function inside_receiver_param bool // indicates if parsing the receiver parameter inside the first `(` and `)` of a method or_is_handled bool // ignore `or` in this expression builtin_mod bool // are we in the `builtin` module? mod string // current module name is_manualfree bool // true when `[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree has_globals bool // `[has_globals] module abc` - allow globals declarations, even without -enable-globals, in that single .v file __only__ is_generated bool // `[generated] module abc` - turn off compiler notices for that single .v file __only__. attrs []ast.Attr // attributes before next decl stmt expr_mod string // for constructing full type names in parse_type() scope &ast.Scope imports map[string]string // alias => mod_name ast_imports []ast.Import // mod_names used_imports []string // alias auto_imports []string // imports, the user does not need to specify imported_symbols map[string]string is_amp bool // for generating the right code for `&Foo{}` returns bool is_stmt_ident bool // true while the beginning of a statement is an ident/selector expecting_type bool // `is Type`, expecting type errors []errors.Error warnings []errors.Warning notices []errors.Notice vet_errors []vet.Error cur_fn_name string label_names []string name_error bool // indicates if the token is not a name or the name is on another line n_asm int // controls assembly labels global_labels []string comptime_if_cond bool defer_vars []ast.Ident should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop codegen_text string } // for tests pub fn parse_stmt(text string, table &ast.Table, scope &ast.Scope) ast.Stmt { mut p := Parser{ scanner: scanner.new_scanner(text, .skip_comments, &pref.Preferences{}) inside_test_file: true table: table pref: &pref.Preferences{} scope: scope } p.init_parse_fns() util.timing_start('PARSE stmt') defer { util.timing_measure_cumulative('PARSE stmt') } p.read_first_token() return p.stmt(false) } pub fn parse_comptime(text string, table &ast.Table, pref &pref.Preferences, scope &ast.Scope) &ast.File { mut p := Parser{ scanner: scanner.new_scanner(text, .skip_comments, pref) table: table pref: pref scope: scope errors: []errors.Error{} warnings: []errors.Warning{} } res := p.parse() unsafe { p.free_scanner() } return res } pub fn parse_text(text string, path string, table &ast.Table, comments_mode scanner.CommentsMode, pref &pref.Preferences) &ast.File { mut p := Parser{ scanner: scanner.new_scanner(text, comments_mode, pref) comments_mode: comments_mode table: table pref: pref scope: &ast.Scope{ start_pos: 0 parent: table.global_scope } errors: []errors.Error{} warnings: []errors.Warning{} } p.set_path(path) res := p.parse() unsafe { p.free_scanner() } return res } [unsafe] pub fn (mut p Parser) free() { unsafe { p.free_scanner() } } [unsafe] pub fn (mut p Parser) free_scanner() { unsafe { if p.scanner != 0 { p.scanner.free() p.scanner = &scanner.Scanner(0) } } } pub fn (mut p Parser) set_path(path string) { p.file_name = path p.file_base = os.base(path) p.file_name_dir = os.dir(path) if p.file_name_dir.contains('vlib') { p.inside_vlib_file = true } hash := fnv1a.sum64_string(path) p.unique_prefix = hash.hex_full() if p.file_base.ends_with('_test.v') || p.file_base.ends_with('_test.vv') { p.inside_test_file = true } before_dot_v := path.all_before_last('.v') // also works for .vv and .vsh language := before_dot_v.all_after_last('.') language_with_underscore := before_dot_v.all_after_last('_') if language == before_dot_v && language_with_underscore == before_dot_v { p.file_backend_mode = .v return } actual_language := if language == before_dot_v { language_with_underscore } else { language } match actual_language { 'c' { p.file_backend_mode = .c } 'js' { p.file_backend_mode = .js } else { arch := pref.arch_from_string(actual_language) or { pref.Arch._auto } p.file_backend_mode = ast.pref_arch_to_table_language(arch) if arch == ._auto { p.file_backend_mode = .v } } } } pub fn parse_file(path string, table &ast.Table, comments_mode scanner.CommentsMode, pref &pref.Preferences) &ast.File { // NB: when comments_mode == .toplevel_comments, // the parser gives feedback to the scanner about toplevel statements, so that the scanner can skip // all the tricky inner comments. This is needed because we do not have a good general solution // for handling them, and should be removed when we do (the general solution is also needed for vfmt) mut p := Parser{ scanner: scanner.new_scanner_file(path, comments_mode, pref) comments_mode: comments_mode table: table pref: pref scope: &ast.Scope{ start_pos: 0 parent: table.global_scope } errors: []errors.Error{} warnings: []errors.Warning{} } p.set_path(path) res := p.parse() unsafe { p.free_scanner() } return res } pub fn parse_vet_file(path string, table_ &ast.Table, pref &pref.Preferences) (&ast.File, []vet.Error) { global_scope := &ast.Scope{ parent: 0 } mut p := Parser{ scanner: scanner.new_scanner_file(path, .parse_comments, pref) comments_mode: .parse_comments table: table_ pref: pref scope: &ast.Scope{ start_pos: 0 parent: global_scope } errors: []errors.Error{} warnings: []errors.Warning{} } p.set_path(path) if p.scanner.text.contains_any_substr(['\n ', ' \n']) { source_lines := os.read_lines(path) or { []string{} } for lnumber, line in source_lines { if line.starts_with(' ') { p.vet_error('Looks like you are using spaces for indentation.', lnumber, .vfmt, .space_indent) } if line.ends_with(' ') { p.vet_error('Looks like you have trailing whitespace.', lnumber, .unknown, .trailing_space) } } } p.vet_errors << p.scanner.vet_errors file := p.parse() unsafe { p.free_scanner() } return file, p.vet_errors } pub fn (mut p Parser) parse() &ast.File { util.timing_start('PARSE') defer { util.timing_measure_cumulative('PARSE') } // comments_mode: comments_mode p.init_parse_fns() p.read_first_token() mut stmts := []ast.Stmt{} for p.tok.kind == .comment { stmts << p.comment_stmt() } // module module_decl := p.module_decl() if module_decl.is_skipped { stmts.insert(0, ast.Stmt(module_decl)) } else { stmts << module_decl } // imports for { if p.tok.kind == .key_import { stmts << p.import_stmt() continue } if p.tok.kind == .comment { stmts << p.comment_stmt() continue } break } for { if p.tok.kind == .eof { p.check_unused_imports() break } stmt := p.top_stmt() // clear the attributes after each statement if !(stmt is ast.ExprStmt && (stmt as ast.ExprStmt).expr is ast.Comment) { p.attrs = [] } stmts << stmt if p.should_abort { break } } p.scope.end_pos = p.tok.pos mut errors := p.errors mut warnings := p.warnings mut notices := p.notices if p.pref.check_only { errors << p.scanner.errors warnings << p.scanner.warnings notices << p.scanner.notices } // codegen if p.codegen_text.len > 0 && !p.pref.is_fmt { ptext := 'module ' + p.mod.all_after_last('.') + p.codegen_text codegen_file := parse_text(ptext, p.file_name, p.table, p.comments_mode, p.pref) stmts << codegen_file.stmts } return &ast.File{ path: p.file_name path_base: p.file_base is_test: p.inside_test_file is_generated: p.is_generated nr_lines: p.scanner.line_nr nr_bytes: p.scanner.text.len mod: module_decl imports: p.ast_imports imported_symbols: p.imported_symbols auto_imports: p.auto_imports stmts: stmts scope: p.scope global_scope: p.table.global_scope errors: errors warnings: warnings notices: notices global_labels: p.global_labels } } /* struct Queue { mut: idx int mu &sync.Mutex mu2 &sync.Mutex paths []string table &ast.Table parsed_ast_files []&ast.File pref &pref.Preferences global_scope &ast.Scope } fn (mut q Queue) run() { for { q.mu.lock() idx := q.idx if idx >= q.paths.len { q.mu.unlock() return } q.idx++ q.mu.unlock() println('run(idx=$idx)') path := q.paths[idx] file := parse_file(path, q.table, .skip_comments, q.pref, q.global_scope) q.mu2.lock() q.parsed_ast_files << file q.mu2.unlock() println('run done(idx=$idx)') } } */ pub fn parse_files(paths []string, table &ast.Table, pref &pref.Preferences) []&ast.File { mut timers := util.new_timers(should_print: false, label: 'parse_files: $paths') $if time_parsing ? { timers.should_print = true } $if macos { /* if !pref.no_parallel && paths[0].contains('/array.v') { println('\n\n\nparse_files() nr_files=$paths.len') println(paths) nr_cpus := runtime.nr_cpus() mut q := &Queue{ paths: paths table: table pref: pref global_scope: global_scope mu: sync.new_mutex() mu2: sync.new_mutex() } for _ in 0 .. nr_cpus - 1 { go q.run() } time.sleep(time.second) println('all done') return q.parsed_ast_files } */ } mut files := []&ast.File{cap: paths.len} for path in paths { timers.start('parse_file $path') files << parse_file(path, table, .skip_comments, pref) timers.show('parse_file $path') } return files } // codegen allows you to generate V code, so that it can be parsed, // checked, markused, cgen-ed etc further, just like user's V code. pub fn (mut p Parser) codegen(code string) { $if debug_codegen ? { eprintln('parser.codegen:\n $code') } p.codegen_text += '\n' + code } pub fn (mut p Parser) init_parse_fns() { // p.prefix_parse_fns = make(100, 100, sizeof(PrefixParseFn)) // p.prefix_parse_fns[token.Kind.name] = parse_name } pub fn (mut p Parser) read_first_token() { // need to call next() 2 times to get peek token and current token p.next() p.next() } [inline] pub fn (p &Parser) peek_token(n int) token.Token { return p.scanner.peek_token(n - 2) } pub fn (mut p Parser) open_scope() { p.scope = &ast.Scope{ parent: p.scope start_pos: p.tok.pos } } pub fn (mut p Parser) close_scope() { // p.scope.end_pos = p.tok.pos // NOTE: since this is usually called after `p.parse_block()` // ie. when `prev_tok` is rcbr `}` we most likely want `prev_tok` // we could do the following, but probably not needed in 99% of cases: // `end_pos = if p.prev_tok.kind == .rcbr { p.prev_tok.pos } else { p.tok.pos }` p.scope.end_pos = p.prev_tok.pos p.scope.parent.children << p.scope p.scope = p.scope.parent } pub fn (mut p Parser) parse_block() []ast.Stmt { p.open_scope() stmts := p.parse_block_no_scope(false) p.close_scope() return stmts } pub fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt { p.check(.lcbr) mut stmts := []ast.Stmt{cap: 20} if p.tok.kind != .rcbr { mut count := 0 for p.tok.kind !in [.eof, .rcbr] { stmts << p.stmt(is_top_level) count++ if count % 100000 == 0 { eprintln('parsed $count statements so far from fn $p.cur_fn_name ...') } if count > 1000000 { p.error_with_pos('parsed over $count statements from fn $p.cur_fn_name, the parser is probably stuck', p.tok.position()) return [] } } } if is_top_level { p.top_level_statement_end() } p.check(.rcbr) return stmts } fn (mut p Parser) next() { p.prev_tok = p.tok p.tok = p.peek_tok p.peek_tok = p.scanner.scan() } fn (mut p Parser) check(expected token.Kind) { p.name_error = false if _likely_(p.tok.kind == expected) { p.next() } else { if expected == .name { p.name_error = true } mut s := expected.str() // quote keywords, punctuation, operators if token.is_key(s) || (s.len > 0 && !s[0].is_letter()) { s = '`$s`' } p.error('unexpected $p.tok, expecting $s') } } // JS functions can have multiple dots in their name: // JS.foo.bar.and.a.lot.more.dots() fn (mut p Parser) check_js_name() string { mut name := '' for p.peek_tok.kind == .dot { name += '${p.tok.lit}.' p.next() // .name p.next() // .dot } // last .name name += p.tok.lit p.next() return name } fn (mut p Parser) check_name() string { name := p.tok.lit if p.peek_tok.kind == .dot && name in p.imports { p.register_used_import(name) } p.check(.name) return name } [if trace_parser ?] fn (p &Parser) trace_parser(label string) { eprintln('parsing: ${p.file_name:-30}|tok.pos: ${p.tok.position().line_str():-39}|tok.kind: ${p.tok.kind:-10}|tok.lit: ${p.tok.lit:-10}|$label') } pub fn (mut p Parser) top_stmt() ast.Stmt { p.trace_parser('top_stmt') for { match p.tok.kind { .key_pub { match p.peek_tok.kind { .key_const { return p.const_decl() } .key_fn { return p.fn_decl() } .key_struct, .key_union { return p.struct_decl() } .key_interface { return p.interface_decl() } .key_enum { return p.enum_decl() } .key_type { return p.type_decl() } else { return p.error('wrong pub keyword usage') } } } .lsbr { // attrs are stored in `p.attrs` p.attributes() continue } .key_asm { return p.asm_stmt(true) } .key_interface { return p.interface_decl() } .key_import { p.error_with_pos('`import x` can only be declared at the beginning of the file', p.tok.position()) return p.import_stmt() } .key_global { return p.global_decl() } .key_const { return p.const_decl() } .key_fn { return p.fn_decl() } .key_struct { return p.struct_decl() } .dollar { if_expr := p.if_expr(true) return ast.ExprStmt{ expr: if_expr pos: if_expr.pos } } .hash { return p.hash() } .key_type { return p.type_decl() } .key_enum { return p.enum_decl() } .key_union { return p.struct_decl() } .comment { return p.comment_stmt() } else { p.inside_fn = true if p.pref.is_script && !p.pref.is_test { p.open_scope() mut stmts := []ast.Stmt{} for p.tok.kind != .eof { stmts << p.stmt(false) } p.close_scope() return ast.FnDecl{ name: 'main.main' mod: 'main' is_main: true stmts: stmts file: p.file_name return_type: ast.void_type scope: p.scope label_names: p.label_names } } else if p.pref.is_fmt { return p.stmt(false) } else { return p.error('bad top level statement ' + p.tok.str()) } } } if p.should_abort { break } } // TODO remove dummy return statement // the compiler complains if it's not there return ast.empty_stmt() } // TODO [if vfmt] pub fn (mut p Parser) check_comment() ast.Comment { if p.tok.kind == .comment { return p.comment() } return ast.Comment{} } pub fn (mut p Parser) comment() ast.Comment { mut pos := p.tok.position() text := p.tok.lit num_newlines := text.count('\n') is_multi := num_newlines > 0 is_inline := text.len + 4 == p.tok.len // 4: `/` `*` `*` `/` pos.last_line = pos.line_nr + num_newlines p.next() // Filter out false positive space indent vet errors inside comments if p.vet_errors.len > 0 && is_multi { p.vet_errors = p.vet_errors.filter(it.typ != .space_indent || it.pos.line_nr - 1 > pos.last_line || it.pos.line_nr - 1 <= pos.line_nr) } return ast.Comment{ text: text is_multi: is_multi is_inline: is_inline pos: pos } } pub fn (mut p Parser) comment_stmt() ast.ExprStmt { comment := p.comment() return ast.ExprStmt{ expr: comment pos: comment.pos } } [params] struct EatCommentsConfig { same_line bool // Only eat comments on the same line as the previous token follow_up bool // Comments directly below the previous token as long as there is no empty line } pub fn (mut p Parser) eat_comments(cfg EatCommentsConfig) []ast.Comment { mut line := p.prev_tok.line_nr mut comments := []ast.Comment{} for { if p.tok.kind != .comment || (cfg.same_line && p.tok.line_nr > line) || (cfg.follow_up && (p.tok.line_nr > line + 1 || p.tok.lit.contains('\n'))) { break } comments << p.comment() if cfg.follow_up { line = p.prev_tok.line_nr } } return comments } pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { p.trace_parser('stmt($is_top_level)') p.is_stmt_ident = p.tok.kind == .name match p.tok.kind { .lcbr { mut pos := p.tok.position() stmts := p.parse_block() pos.last_line = p.prev_tok.line_nr return ast.Block{ stmts: stmts pos: pos } } .key_assert { p.next() mut pos := p.tok.position() expr := p.expr(0) pos.update_last_line(p.prev_tok.line_nr) return ast.AssertStmt{ expr: expr pos: pos.extend(p.tok.position()) is_used: p.inside_test_file || !p.pref.is_prod } } .key_for { return p.for_stmt() } .name { if p.tok.lit == 'sql' && p.peek_tok.kind == .name { return p.sql_stmt() } if p.peek_tok.kind == .colon { // `label:` spos := p.tok.position() name := p.check_name() if name in p.label_names { p.error_with_pos('duplicate label `$name`', spos) } p.label_names << name p.next() if p.tok.kind == .key_for { for_pos := p.tok.position() mut stmt := p.stmt(is_top_level) match mut stmt { ast.ForStmt { stmt.label = name return stmt } ast.ForInStmt { stmt.label = name return stmt } ast.ForCStmt { stmt.label = name return stmt } else { p.error_with_pos('unknown kind of For statement', for_pos) } } } return ast.GotoLabel{ name: name pos: spos.extend(p.tok.position()) } } else if p.peek_tok.kind == .name { return p.error_with_pos('unexpected name `$p.tok.lit`', p.tok.position()) } else if !p.inside_if_expr && !p.inside_match_body && !p.inside_or_expr && p.peek_tok.kind in [.rcbr, .eof] && !p.mark_var_as_used(p.tok.lit) { return p.error_with_pos('`$p.tok.lit` evaluated but not used', p.tok.position()) } return p.parse_multi_expr(is_top_level) } .comment { return p.comment_stmt() } .key_return { if p.inside_defer { return p.error_with_pos('`return` not allowed inside `defer` block', p.tok.position()) } else { return p.return_stmt() } } .dollar { match p.peek_tok.kind { .key_if { mut pos := p.tok.position() expr := p.if_expr(true) pos.update_last_line(p.prev_tok.line_nr) return ast.ExprStmt{ expr: expr pos: pos } } .key_for { return p.comptime_for() } .name { mut pos := p.tok.position() expr := p.comptime_call() pos.update_last_line(p.prev_tok.line_nr) return ast.ExprStmt{ expr: expr pos: pos } } else { return p.error_with_pos('unexpected \$', p.tok.position()) } } } .key_continue, .key_break { tok := p.tok line := p.tok.line_nr p.next() mut label := '' if p.tok.line_nr == line && p.tok.kind == .name { label = p.check_name() } return ast.BranchStmt{ kind: tok.kind label: label pos: tok.position() } } .key_unsafe { return p.unsafe_stmt() } .hash { return p.hash() } .key_defer { if p.inside_defer { return p.error_with_pos('`defer` blocks cannot be nested', p.tok.position()) } else { p.next() spos := p.tok.position() p.inside_defer = true p.defer_vars = []ast.Ident{} stmts := p.parse_block() p.inside_defer = false return ast.DeferStmt{ stmts: stmts defer_vars: p.defer_vars.clone() pos: spos.extend_with_last_line(p.tok.position(), p.prev_tok.line_nr) } } } .key_go { go_expr := p.go_expr() return ast.ExprStmt{ expr: go_expr pos: go_expr.pos } } .key_goto { p.next() spos := p.tok.position() name := p.check_name() return ast.GotoStmt{ name: name pos: spos } } .key_const { return p.error_with_pos('const can only be defined at the top level (outside of functions)', p.tok.position()) } .key_asm { return p.asm_stmt(false) } // literals, 'if', etc. in here else { return p.parse_multi_expr(is_top_level) } } } fn (mut p Parser) asm_stmt(is_top_level bool) ast.AsmStmt { p.inside_asm = true p.inside_asm_template = true defer { p.inside_asm = false p.inside_asm_template = false } p.n_asm = 0 if is_top_level { p.top_level_statement_start() } mut backup_scope := p.scope pos := p.tok.position() p.check(.key_asm) mut arch := pref.arch_from_string(p.tok.lit) or { pref.Arch._auto } mut is_volatile := false mut is_goto := false if p.tok.kind == .key_volatile { arch = pref.arch_from_string(p.peek_tok.lit) or { pref.Arch._auto } is_volatile = true p.next() } else if p.tok.kind == .key_goto { arch = pref.arch_from_string(p.peek_tok.lit) or { pref.Arch._auto } is_goto = true p.next() } if arch == ._auto && !p.pref.is_fmt { if p.tok.lit == '' { p.error('missing assembly architecture. Try i386, amd64 or arm64.') } p.error('unknown assembly architecture') } if p.tok.kind != .name { p.error('must specify assembly architecture') } else { p.next() } p.check_for_impure_v(ast.pref_arch_to_table_language(arch), p.prev_tok.position()) p.check(.lcbr) p.scope = &ast.Scope{ parent: 0 // you shouldn't be able to reference other variables in assembly blocks detached_from_parent: true start_pos: p.tok.pos objects: ast.all_registers(mut p.table, arch) // } mut local_labels := []string{} // riscv: https://github.com/jameslzhu/riscv-card/blob/master/riscv-card.pdf // x86: https://www.felixcloutier.com/x86/ // arm: https://developer.arm.com/documentation/dui0068/b/arm-instruction-reference mut templates := []ast.AsmTemplate{} for p.tok.kind !in [.semicolon, .rcbr] { template_pos := p.tok.position() mut name := '' if p.tok.kind == .name && arch == .amd64 && p.tok.lit in ['rex', 'vex', 'xop'] { name += p.tok.lit p.next() for p.tok.kind == .dot { p.next() name += '.' + p.tok.lit p.check(.name) } name += ' ' } is_directive := p.tok.kind == .dot if is_directive { p.next() } if p.tok.kind in [.key_in, .key_lock, .key_orelse] { // `in`, `lock`, `or` are v keywords that are also x86/arm/riscv instructions. name += p.tok.kind.str() p.next() } else if p.tok.kind == .number { name += p.tok.lit p.next() } else { name += p.tok.lit p.check(.name) } // dots are part of instructions for some riscv extensions if arch in [.rv32, .rv64] { for p.tok.kind == .dot { name += '.' p.next() name += p.tok.lit p.check(.name) } } mut is_label := false mut args := []ast.AsmArg{} if p.tok.line_nr == p.prev_tok.line_nr { args_loop: for { if p.prev_tok.position().line_nr < p.tok.position().line_nr { break } mut segment := '' if p.tok.kind == .name && p.peek_tok.kind == .colon { segment = p.tok.lit p.next() p.next() } match p.tok.kind { .name { args << p.reg_or_alias() } .number { number_lit := p.parse_number_literal() match number_lit { ast.FloatLiteral { args << ast.FloatLiteral{ ...number_lit } } ast.IntegerLiteral { if is_directive { args << ast.AsmDisp{ val: number_lit.val pos: number_lit.pos } } else { args << ast.IntegerLiteral{ ...number_lit } } } else { verror('p.parse_number_literal() invalid output: `$number_lit`') } } } .chartoken { args << ast.CharLiteral{ val: p.tok.lit pos: p.tok.position() } p.next() } .colon { is_label = true p.next() local_labels << name break } .lsbr { mut addressing := p.asm_addressing() addressing.segment = segment args << addressing } .rcbr { break } .semicolon { break } else { p.error('invalid token in assembly block') } } if p.tok.kind == .comma { p.next() } else { break } } // if p.prev_tok.position().line_nr < p.tok.position().line_nr { // break // } } mut comments := []ast.Comment{} for p.tok.kind == .comment { comments << p.comment() } if is_directive && name in ['globl', 'global'] { for arg in args { p.global_labels << (arg as ast.AsmAlias).name } } templates << ast.AsmTemplate{ name: name args: args comments: comments is_label: is_label is_directive: is_directive pos: template_pos.extend(p.tok.position()) } } mut scope := p.scope p.scope = backup_scope p.inside_asm_template = false mut output, mut input, mut clobbered, mut global_labels := []ast.AsmIO{}, []ast.AsmIO{}, []ast.AsmClobbered{}, []string{} if !is_top_level { if p.tok.kind == .semicolon { output = p.asm_ios(true) if p.tok.kind == .semicolon { input = p.asm_ios(false) } if p.tok.kind == .semicolon { // because p.reg_or_alias() requires the scope with registers to recognize registers. backup_scope = p.scope p.scope = scope p.next() for p.tok.kind == .name { reg := ast.AsmRegister{ name: p.tok.lit typ: 0 size: -1 } p.next() mut comments := []ast.Comment{} for p.tok.kind == .comment { comments << p.comment() } clobbered << ast.AsmClobbered{ reg: reg comments: comments } if p.tok.kind in [.rcbr, .semicolon] { break } } if is_goto && p.tok.kind == .semicolon { p.next() for p.tok.kind == .name { global_labels << p.tok.lit p.next() } } } } } else if p.tok.kind == .semicolon { p.error('extended assembly is not allowed as a top level statement') } p.scope = backup_scope p.check(.rcbr) if is_top_level { p.top_level_statement_end() } scope.end_pos = p.prev_tok.pos return ast.AsmStmt{ arch: arch is_goto: is_goto is_volatile: is_volatile templates: templates output: output input: input clobbered: clobbered pos: pos.extend(p.prev_tok.position()) is_basic: is_top_level || output.len + input.len + clobbered.len == 0 scope: scope global_labels: global_labels local_labels: local_labels } } fn (mut p Parser) reg_or_alias() ast.AsmArg { p.check(.name) if p.prev_tok.lit in p.scope.objects { x := unsafe { p.scope.objects[p.prev_tok.lit] } if x is ast.AsmRegister { return ast.AsmArg(x as ast.AsmRegister) } else { verror('non-register ast.ScopeObject found in scope') return ast.AsmDisp{} // should not be reached } } else if p.prev_tok.len >= 2 && p.prev_tok.lit[0] in [`b`, `f`] && p.prev_tok.lit[1..].bytes().all(it.is_digit()) { return ast.AsmDisp{ val: p.prev_tok.lit[1..] + p.prev_tok.lit[0].ascii_str() } } else { return ast.AsmAlias{ name: p.prev_tok.lit pos: p.prev_tok.position() } } } // fn (mut p Parser) asm_addressing() ast.AsmAddressing { // pos := p.tok.position() // p.check(.lsbr) // unknown_addressing_mode := 'unknown addressing mode. supported ones are [displacement], [base], [base + displacement] [index ∗ scale + displacement], [base + index ∗ scale + displacement], [base + index + displacement] [rip + displacement]' // mut mode := ast.AddressingMode.invalid // if p.peek_tok.kind == .rsbr { // if p.tok.kind == .name { // mode = .base // } else if p.tok.kind == .number { // mode = .displacement // } else { // p.error(unknown_addressing_mode) // } // } else if p.peek_tok.kind == .mul { // mode = .index_times_scale_plus_displacement // } else if p.tok.lit == 'rip' { // mode = .rip_plus_displacement // } else if p.peek_tok3.kind == .mul { // mode = .base_plus_index_times_scale_plus_displacement // } else if p.peek_tok.kind == .plus && p.peek_tok3.kind == .rsbr { // mode = .base_plus_displacement // } else if p.peek_tok.kind == .plus && p.peek_tok3.kind == .plus { // mode = .base_plus_index_plus_displacement // } else { // p.error(unknown_addressing_mode) // } // mut displacement, mut base, mut index, mut scale := u32(0), ast.AsmArg{}, ast.AsmArg{}, -1 // match mode { // .base { // base = p.reg_or_alias() // } // .displacement { // displacement = p.tok.lit.u32() // p.check(.number) // } // .base_plus_displacement { // base = p.reg_or_alias() // p.check(.plus) // displacement = p.tok.lit.u32() // p.check(.number) // } // .index_times_scale_plus_displacement { // index = p.reg_or_alias() // p.check(.mul) // scale = p.tok.lit.int() // p.check(.number) // p.check(.plus) // displacement = p.tok.lit.u32() // p.check(.number) // } // .base_plus_index_times_scale_plus_displacement { // base = p.reg_or_alias() // p.check(.plus) // index = p.reg_or_alias() // p.check(.mul) // scale = p.tok.lit.int() // p.check(.number) // p.check(.plus) // displacement = p.tok.lit.u32() // p.check(.number) // } // .rip_plus_displacement { // base = p.reg_or_alias() // p.check(.plus) // displacement = p.tok.lit.u32() // p.check(.number) // } // .base_plus_index_plus_displacement { // base = p.reg_or_alias() // p.check(.plus) // index = p.reg_or_alias() // p.check(.plus) // displacement = p.tok.lit.u32() // p.check(.number) // } // .invalid {} // there was already an error above // } // p.check(.rsbr) // return ast.AsmAddressing{ // base: base // displacement: displacement // index: index // scale: scale // mode: mode // pos: pos.extend(p.prev_tok.position()) // } // } fn (mut p Parser) asm_addressing() ast.AsmAddressing { pos := p.tok.position() p.check(.lsbr) unknown_addressing_mode := 'unknown addressing mode. supported ones are [displacement], [base], [base + displacement], [index ∗ scale + displacement], [base + index ∗ scale + displacement], [base + index + displacement], [rip + displacement]' // this mess used to look much cleaner before the removal of peek_tok2/3, see above code for cleaner version if p.peek_tok.kind == .rsbr { // [displacement] or [base] if p.tok.kind == .name { base := p.reg_or_alias() p.check(.rsbr) return ast.AsmAddressing{ mode: .base base: base pos: pos.extend(p.prev_tok.position()) } } else if p.tok.kind == .number { displacement := if p.tok.kind == .name { p.reg_or_alias() } else { x := ast.AsmArg(ast.AsmDisp{ val: p.tok.lit pos: p.tok.position() }) p.check(.number) x } p.check(.rsbr) return ast.AsmAddressing{ mode: .displacement displacement: displacement pos: pos.extend(p.prev_tok.position()) } } else { p.error(unknown_addressing_mode) } } if p.peek_tok.kind == .plus && p.tok.kind == .name { // [base + displacement], [base + index ∗ scale + displacement], [base + index + displacement] or [rip + displacement] if p.tok.lit == 'rip' { rip := p.reg_or_alias() p.next() displacement := if p.tok.kind == .name { p.reg_or_alias() } else { x := ast.AsmArg(ast.AsmDisp{ val: p.tok.lit pos: p.tok.position() }) p.check(.number) x } p.check(.rsbr) return ast.AsmAddressing{ mode: .rip_plus_displacement base: rip displacement: displacement pos: pos.extend(p.prev_tok.position()) } } base := p.reg_or_alias() p.next() if p.peek_tok.kind == .rsbr { if p.tok.kind == .number { displacement := if p.tok.kind == .name { p.reg_or_alias() } else { x := ast.AsmArg(ast.AsmDisp{ val: p.tok.lit pos: p.tok.position() }) p.check(.number) x } p.check(.rsbr) return ast.AsmAddressing{ mode: .base_plus_displacement base: base displacement: displacement pos: pos.extend(p.prev_tok.position()) } } else { p.error(unknown_addressing_mode) } } index := p.reg_or_alias() if p.tok.kind == .mul { p.next() scale := p.tok.lit.int() p.check(.number) p.check(.plus) displacement := if p.tok.kind == .name { p.reg_or_alias() } else { x := ast.AsmArg(ast.AsmDisp{ val: p.tok.lit pos: p.tok.position() }) p.check(.number) x } p.check(.rsbr) return ast.AsmAddressing{ mode: .base_plus_index_times_scale_plus_displacement base: base index: index scale: scale displacement: displacement pos: pos.extend(p.prev_tok.position()) } } else if p.tok.kind == .plus { p.next() displacement := if p.tok.kind == .name { p.reg_or_alias() } else { x := ast.AsmArg(ast.AsmDisp{ val: p.tok.lit pos: p.tok.position() }) p.check(.number) x } p.check(.rsbr) return ast.AsmAddressing{ mode: .base_plus_index_plus_displacement base: base index: index displacement: displacement pos: pos.extend(p.prev_tok.position()) } } } if p.peek_tok.kind == .mul { // [index ∗ scale + displacement] index := p.reg_or_alias() p.next() scale := p.tok.lit.int() p.check(.number) p.check(.plus) displacement := if p.tok.kind == .name { p.reg_or_alias() } else { x := ast.AsmArg(ast.AsmDisp{ val: p.tok.lit pos: p.tok.position() }) p.check(.number) x } p.check(.rsbr) return ast.AsmAddressing{ mode: .index_times_scale_plus_displacement index: index scale: scale displacement: displacement pos: pos.extend(p.prev_tok.position()) } } p.error(unknown_addressing_mode) return ast.AsmAddressing{} } fn (mut p Parser) asm_ios(output bool) []ast.AsmIO { mut res := []ast.AsmIO{} p.check(.semicolon) if p.tok.kind in [.rcbr, .semicolon] { return [] } for { pos := p.tok.position() mut constraint := '' if p.tok.kind == .lpar { constraint = if output { '+r' } else { 'r' } // default constraint, though vfmt fmts to `+r` and `r` } else { constraint += match p.tok.kind { .assign { '=' } .plus { '+' } .mod { '%' } .amp { '&' } else { '' } } if constraint != '' { p.next() } constraint += p.tok.lit if p.tok.kind == .at { p.next() } else { p.check(.name) } } mut expr := p.expr(0) if mut expr is ast.ParExpr { expr = expr.expr } else { p.error('asm in/output must be enclosed in brackets') } mut alias := '' if p.tok.kind == .key_as { p.next() alias = p.tok.lit p.check(.name) } else if mut expr is ast.Ident { alias = expr.name } // for constraints like `a`, no alias is needed, it is reffered to as rcx mut comments := []ast.Comment{} for p.tok.kind == .comment { comments << p.comment() } res << ast.AsmIO{ alias: alias constraint: constraint expr: expr comments: comments pos: pos.extend(p.prev_tok.position()) } p.n_asm++ if p.tok.kind in [.semicolon, .rcbr] { break } } return res } fn (mut p Parser) expr_list() ([]ast.Expr, []ast.Comment) { mut exprs := []ast.Expr{} mut comments := []ast.Comment{} for { expr := p.expr(0) if expr is ast.Comment { comments << expr } else { exprs << expr if p.tok.kind != .comma { break } p.next() } } return exprs, comments } fn (mut p Parser) is_attributes() bool { if p.tok.kind != .lsbr { return false } mut i := 0 for { tok := p.peek_token(i) if tok.kind == .eof || tok.line_nr != p.tok.line_nr { return false } if tok.kind == .rsbr { break } i++ } peek_rsbr_tok := p.peek_token(i + 1) if peek_rsbr_tok.line_nr == p.tok.line_nr && peek_rsbr_tok.kind != .rcbr { return false } return true } // when is_top_stmt is true attrs are added to p.attrs fn (mut p Parser) attributes() { p.check(.lsbr) mut has_ctdefine := false for p.tok.kind != .rsbr { start_pos := p.tok.position() attr := p.parse_attr() if p.attrs.contains(attr.name) && attr.name != 'wasm_export' { p.error_with_pos('duplicate attribute `$attr.name`', start_pos.extend(p.prev_tok.position())) return } if attr.kind == .comptime_define { if has_ctdefine { p.error_with_pos('only one `[if flag]` may be applied at a time `$attr.name`', start_pos.extend(p.prev_tok.position())) return } else { has_ctdefine = true } } p.attrs << attr if p.tok.kind != .semicolon { if p.tok.kind == .rsbr { p.next() break } p.error('unexpected $p.tok, expecting `;`') return } p.next() } if p.attrs.len == 0 { p.error_with_pos('attributes cannot be empty', p.prev_tok.position().extend(p.tok.position())) return } } fn (mut p Parser) parse_attr() ast.Attr { mut kind := ast.AttrKind.plain apos := p.prev_tok.position() if p.tok.kind == .key_unsafe { p.next() return ast.Attr{ name: 'unsafe' kind: kind pos: apos.extend(p.tok.position()) } } mut name := '' mut has_arg := false mut arg := '' mut comptime_cond := ast.empty_expr() mut comptime_cond_opt := false if p.tok.kind == .key_if { kind = .comptime_define p.next() p.comptime_if_cond = true p.inside_if_expr = true p.inside_ct_if_expr = true comptime_cond = p.expr(0) p.comptime_if_cond = false p.inside_if_expr = false p.inside_ct_if_expr = false if comptime_cond is ast.PostfixExpr { comptime_cond_opt = true } name = comptime_cond.str() } else if p.tok.kind == .string { name = p.tok.lit kind = .string p.next() } else { name = p.check_name() // support dot prefix `module.name: arg` if p.tok.kind == .dot { p.next() name += '.' name += p.check_name() } if p.tok.kind == .colon { has_arg = true p.next() if p.tok.kind == .name { // `name: arg` kind = .plain arg = p.check_name() } else if p.tok.kind == .number { // `name: 123` kind = .number arg = p.tok.lit p.next() } else if p.tok.kind == .string { // `name: 'arg'` kind = .string arg = p.tok.lit p.next() } else if p.tok.kind == .key_true || p.tok.kind == .key_false { // `name: true` kind = .bool arg = p.tok.kind.str() p.next() } else { p.error('unexpected $p.tok, an argument is expected after `:`') } } } return ast.Attr{ name: name has_arg: has_arg arg: arg kind: kind ct_expr: comptime_cond ct_opt: comptime_cond_opt pos: apos.extend(p.tok.position()) } } pub fn (mut p Parser) language_not_allowed_error(language ast.Language, pos token.Position) { upcase_language := language.str().to_upper() p.error_with_pos('$upcase_language code is not allowed in .${p.file_backend_mode}.v files, please move it to a .${language}.v file', pos) } pub fn (mut p Parser) language_not_allowed_warning(language ast.Language, pos token.Position) { upcase_language := language.str().to_upper() p.warn_with_pos('$upcase_language code will not be allowed in pure .v files, please move it to a .${language}.v file instead', pos) } pub fn (mut p Parser) check_for_impure_v(language ast.Language, pos token.Position) { if language == .v { // pure V code is always allowed everywhere return } else { match p.file_backend_mode { .c { if language != .c { p.language_not_allowed_error(language, pos) return } } .js { if language != .js { p.language_not_allowed_error(language, pos) return } } else {} } } if !p.pref.warn_impure_v { // the stricter mode is not ON yet => allow everything for now return } if p.file_backend_mode != language { if p.file_backend_mode == .v { p.language_not_allowed_warning(language, pos) return } } } pub fn (mut p Parser) error(s string) ast.NodeError { return p.error_with_pos(s, p.tok.position()) } pub fn (mut p Parser) warn(s string) { p.warn_with_pos(s, p.tok.position()) } pub fn (mut p Parser) note(s string) { p.note_with_pos(s, p.tok.position()) } pub fn (mut p Parser) error_with_pos(s string, pos token.Position) ast.NodeError { if p.pref.fatal_errors { exit(1) } mut kind := 'error:' if p.pref.output_mode == .stdout && !p.pref.check_only { if p.pref.is_verbose { print_backtrace() kind = 'parser error:' } ferror := util.formatted_error(kind, s, p.file_name, pos) eprintln(ferror) exit(1) } else { p.errors << errors.Error{ file_path: p.file_name pos: pos reporter: .parser message: s } // To avoid getting stuck after an error, the parser // will proceed to the next token. if p.pref.check_only { p.next() } } if p.pref.output_mode == .silent { // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. // In the silent mode however, the parser continues to run, even though it would have stopped. Some // of the parser logic does not expect that, and may loop forever. // The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode. p.next() } return ast.NodeError{ idx: p.errors.len - 1 pos: pos } } pub fn (mut p Parser) error_with_error(error errors.Error) { if p.pref.fatal_errors { exit(1) } mut kind := 'error:' if p.pref.output_mode == .stdout && !p.pref.check_only { if p.pref.is_verbose { print_backtrace() kind = 'parser error:' } ferror := util.formatted_error(kind, error.message, error.file_path, error.pos) eprintln(ferror) exit(1) } else { if p.pref.message_limit >= 0 && p.errors.len >= p.pref.message_limit { p.should_abort = true return } p.errors << error } if p.pref.output_mode == .silent { // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error. // In the silent mode however, the parser continues to run, even though it would have stopped. Some // of the parser logic does not expect that, and may loop forever. // The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode. p.next() } } pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) { if p.pref.warns_are_errors { p.error_with_pos(s, pos) return } if p.pref.skip_warnings { return } if p.pref.output_mode == .stdout && !p.pref.check_only { ferror := util.formatted_error('warning:', s, p.file_name, pos) eprintln(ferror) } else { if p.pref.message_limit >= 0 && p.warnings.len >= p.pref.message_limit { p.should_abort = true return } p.warnings << errors.Warning{ file_path: p.file_name pos: pos reporter: .parser message: s } } } pub fn (mut p Parser) note_with_pos(s string, pos token.Position) { if p.pref.skip_warnings { return } if p.is_generated { return } if p.pref.output_mode == .stdout && !p.pref.check_only { ferror := util.formatted_error('notice:', s, p.file_name, pos) eprintln(ferror) } else { p.notices << errors.Notice{ file_path: p.file_name pos: pos reporter: .parser message: s } } } pub fn (mut p Parser) vet_error(msg string, line int, fix vet.FixKind, typ vet.ErrorType) { pos := token.Position{ line_nr: line + 1 } p.vet_errors << vet.Error{ message: msg file_path: p.scanner.file_path pos: pos kind: .error fix: fix typ: typ } } fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt { // in here might be 1) multi-expr 2) multi-assign // 1, a, c ... } // multi-expression // a, mut b ... :=/= // multi-assign // collect things upto hard boundaries tok := p.tok mut pos := tok.position() mut defer_vars := p.defer_vars p.defer_vars = []ast.Ident{} left, left_comments := p.expr_list() if !(p.inside_defer && p.tok.kind == .decl_assign) { defer_vars << p.defer_vars } p.defer_vars = defer_vars left0 := left[0] if tok.kind == .key_mut && p.tok.kind != .decl_assign { return p.error('expecting `:=` (e.g. `mut x :=`)') } // TODO remove translated if p.tok.kind in [.assign, .decl_assign] || p.tok.kind.is_assign() { return p.partial_assign_stmt(left, left_comments) } else if !p.pref.translated && !p.pref.is_fmt && tok.kind !in [.key_if, .key_match, .key_lock, .key_rlock, .key_select] { for node in left { if (is_top_level || p.tok.kind != .rcbr) && node !is ast.CallExpr && node !is ast.PostfixExpr && node !is ast.ComptimeCall && node !is ast.SelectorExpr && node !is ast.DumpExpr { is_complex_infix_expr := node is ast.InfixExpr && (node as ast.InfixExpr).op in [.left_shift, .right_shift, .unsigned_right_shift, .arrow] if !is_complex_infix_expr { return p.error_with_pos('expression evaluated but not used', node.position()) } } } } pos.update_last_line(p.prev_tok.line_nr) if left.len == 1 { return ast.ExprStmt{ expr: left0 pos: left0.position() comments: left_comments is_expr: p.inside_for } } return ast.ExprStmt{ expr: ast.ConcatExpr{ vals: left pos: tok.position() } pos: pos comments: left_comments } } pub fn (mut p Parser) parse_ident(language ast.Language) ast.Ident { // p.warn('name ') is_shared := p.tok.kind == .key_shared is_atomic := p.tok.kind == .key_atomic if is_shared { p.register_auto_import('sync') } mut_pos := p.tok.position() is_mut := p.tok.kind == .key_mut || is_shared || is_atomic if is_mut { p.next() } is_static := p.tok.kind == .key_static if is_static { p.next() } is_volatile := p.tok.kind == .key_volatile if is_volatile { p.next() } if p.tok.kind != .name { p.error('unexpected token `$p.tok.lit`') return ast.Ident{ scope: p.scope } } pos := p.tok.position() mut name := p.check_name() if name == '_' { return ast.Ident{ tok_kind: p.tok.kind name: '_' comptime: p.comptime_if_cond kind: .blank_ident pos: pos info: ast.IdentVar{ is_mut: false is_static: false is_volatile: false } scope: p.scope } } if p.inside_match_body && name == 'it' { // p.warn('it') } if p.expr_mod.len > 0 { name = '${p.expr_mod}.$name' } return ast.Ident{ tok_kind: p.tok.kind kind: .unresolved name: name comptime: p.comptime_if_cond language: language mod: p.mod pos: pos is_mut: is_mut mut_pos: mut_pos info: ast.IdentVar{ is_mut: is_mut is_static: is_static is_volatile: is_volatile share: ast.sharetype_from_flags(is_shared, is_atomic) } scope: p.scope } } fn (p &Parser) is_typename(t token.Token) bool { return t.kind == .name && (t.lit[0].is_capital() || p.table.known_type(t.lit)) } // heuristics to detect `func()` from `var < expr` // 1. `f<[]` is generic(e.g. `f<[]int>`) because `var < []` is invalid // 2. `f) // 3. `f` is generic because `v1 < foo > v2` is invalid syntax // 4. `f(), in contrast, is not generic!) // 5. `f` is same as case 3 // 7. `f 0 && p.tok.lit[0].is_capital() if lit0_is_capital || p.peek_tok.kind != .lt { return false } mut tok2 := p.peek_token(2) mut tok3 := p.peek_token(3) mut tok4 := p.peek_token(4) mut tok5 := p.peek_token(5) mut kind2, mut kind3, mut kind4, mut kind5 := tok2.kind, tok3.kind, tok4.kind, tok5.kind if kind2 == .amp { // if there is a & in front, shift everything left tok2 = tok3 kind2 = kind3 tok3 = tok4 kind3 = kind4 tok4 = tok5 kind4 = kind5 tok5 = p.peek_token(6) kind5 = tok5.kind } if kind2 == .lsbr { // case 1 return tok3.kind == .rsbr } if kind2 == .name { if tok2.lit == 'map' && kind3 == .lsbr { // case 2 return true } return match kind3 { .gt { true } // case 3 .lt { !(tok4.lit.len == 1 && tok4.lit[0].is_capital()) } // case 4 .comma { p.is_typename(tok2) } // case 5 // case 6 and 7 .dot { kind4 == .name && (kind5 == .gt || (kind5 == .comma && p.is_typename(tok4))) } else { false } } } return false } const valid_tokens_inside_types = [token.Kind.lsbr, .rsbr, .name, .dot, .comma, .key_fn, .lt] fn (mut p Parser) is_generic_cast() bool { if !ast.type_can_start_with_token(p.tok) { return false } mut i := 0 mut level := 0 mut lt_count := 0 for { i++ tok := p.peek_token(i) if tok.kind == .lt { lt_count++ level++ } else if tok.kind == .gt { level-- } if lt_count > 0 && level == 0 { break } if i > 20 || tok.kind !in parser.valid_tokens_inside_types { return false } } next_tok := p.peek_token(i + 1) // `next_tok` is the token following the closing `>` of the generic type: MyType{ // ^ // if `next_tok` is a left paren, then the full expression looks something like // `Foo(` or `Foo(`, which are valid type casts - return true if next_tok.kind == .lpar { return true } // any other token is not a valid generic cast, however return false } pub fn (mut p Parser) name_expr() ast.Expr { prev_tok_kind := p.prev_tok.kind mut node := ast.empty_expr() if p.expecting_type { p.expecting_type = false // get type position before moving to next type_pos := p.tok.position() typ := p.parse_type() return ast.TypeNode{ typ: typ pos: type_pos } } mut language := ast.Language.v if p.tok.lit == 'C' { language = ast.Language.c p.check_for_impure_v(language, p.tok.position()) } else if p.tok.lit == 'JS' { language = ast.Language.js p.check_for_impure_v(language, p.tok.position()) } mut mod := '' // p.warn('resetting') p.expr_mod = '' // `map[string]int` initialization if p.tok.lit == 'map' && p.peek_tok.kind == .lsbr { map_type := p.parse_map_type() if p.tok.kind == .lcbr { p.next() if p.tok.kind == .rcbr { p.next() } else { if p.pref.is_fmt { map_init := p.map_init() p.check(.rcbr) return map_init } p.error('`}` expected; explicit `map` initialization does not support parameters') } } return ast.MapInit{ typ: map_type pos: p.prev_tok.position() } } // `chan typ{...}` if p.tok.lit == 'chan' { first_pos := p.tok.position() mut last_pos := first_pos chan_type := p.parse_chan_type() mut has_cap := false mut cap_expr := ast.empty_expr() p.check(.lcbr) if p.tok.kind == .rcbr { last_pos = p.tok.position() p.next() } else { key := p.check_name() p.check(.colon) match key { 'cap' { has_cap = true cap_expr = p.expr(0) } 'len', 'init' { return p.error('`$key` cannot be initialized for `chan`. Did you mean `cap`?') } else { return p.error('wrong field `$key`, expecting `cap`') } } last_pos = p.tok.position() p.check(.rcbr) } return ast.ChanInit{ pos: first_pos.extend(last_pos) has_cap: has_cap cap_expr: cap_expr typ: chan_type } } // Raw string (`s := r'hello \n ') if p.peek_tok.kind == .string && !p.inside_str_interp && p.peek_token(2).kind != .colon { if p.tok.lit in ['r', 'c', 'js'] && p.tok.kind == .name { return p.string_expr() } else { // don't allow any other string prefix except `r`, `js` and `c` return p.error('only `c`, `r`, `js` are recognized string prefixes, but you tried to use `$p.tok.lit`') } } // don't allow r`byte` and c`byte` if p.tok.lit in ['r', 'c'] && p.peek_tok.kind == .chartoken { opt := if p.tok.lit == 'r' { '`r` (raw string)' } else { '`c` (c string)' } return p.error('cannot use $opt with `byte` and `rune`') } // Make sure that the var is not marked as used in assignments: `x = 1`, `x += 2` etc // but only when it's actually used (e.g. `println(x)`) known_var := if p.peek_tok.kind.is_assign() { p.scope.known_var(p.tok.lit) } else { p.mark_var_as_used(p.tok.lit) } // Handle modules mut is_mod_cast := false if p.peek_tok.kind == .dot && !known_var && (language != .v || p.known_import(p.tok.lit) || p.mod.all_after_last('.') == p.tok.lit) { // p.tok.lit has been recognized as a module if language == .c { mod = 'C' } else if language == .js { mod = 'JS' } else { if p.tok.lit in p.imports { // mark the imported module as used p.register_used_import(p.tok.lit) if p.peek_tok.kind == .dot && p.peek_token(2).kind != .eof && p.peek_token(2).lit.len > 0 && p.peek_token(2).lit[0].is_capital() { is_mod_cast = true } else if p.peek_tok.kind == .dot && p.peek_token(2).kind != .eof && p.peek_token(2).lit.len == 0 { // incomplete module selector must be handled by dot_expr instead ident := p.parse_ident(language) node = ident if p.inside_defer { if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) && ident.name != 'err' { p.defer_vars << ident } } return node } } // prepend the full import mod = p.imports[p.tok.lit] } p.next() p.check(.dot) p.expr_mod = mod } lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 { p.tok.lit[0].is_capital() } else { false } is_optional := p.tok.kind == .question is_generic_call := p.is_generic_call() is_generic_cast := p.is_generic_cast() // p.warn('name expr $p.tok.lit $p.peek_tok.str()') same_line := p.tok.line_nr == p.peek_tok.line_nr // `(` must be on same line as name token otherwise it's a ParExpr if !same_line && p.peek_tok.kind == .lpar { ident := p.parse_ident(language) node = ident if p.inside_defer { if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) && ident.name != 'err' { p.defer_vars << ident } } } else if p.peek_tok.kind == .lpar || is_generic_call || is_generic_cast || (is_optional && p.peek_token(2).kind == .lpar) { // foo(), foo() or type() cast mut name := if is_optional { p.peek_tok.lit } else { p.tok.lit } if mod.len > 0 { name = '${mod}.$name' } name_w_mod := p.prepend_mod(name) // type cast. TODO: finish // if name in ast.builtin_type_names_to_idx { if (!known_var && (name in p.table.type_idxs || name_w_mod in p.table.type_idxs) && name !in ['C.stat', 'C.sigaction']) || is_mod_cast || is_generic_cast || (language == .v && name.len > 0 && name[0].is_capital()) { // MainLetter(x) is *always* a cast, as long as it is not `C.` // TODO handle C.stat() start_pos := p.tok.position() mut to_typ := p.parse_type() // this prevents inner casts to also have an `&` // example: &Foo(malloc(int(num))) // without the next line int would result in int* p.is_amp = false p.check(.lpar) mut expr := ast.empty_expr() mut arg := ast.empty_expr() mut has_arg := false expr = p.expr(0) // TODO, string(b, len) if p.tok.kind == .comma && to_typ.idx() == ast.string_type_idx { p.next() arg = p.expr(0) // len has_arg = true } end_pos := p.tok.position() p.check(.rpar) node = ast.CastExpr{ typ: to_typ typname: p.table.sym(to_typ).name expr: expr arg: arg has_arg: has_arg pos: start_pos.extend(end_pos) } p.expr_mod = '' return node } else { // fn call if is_optional { p.error_with_pos('unexpected $p.prev_tok', p.prev_tok.position()) } node = p.call_expr(language, mod) } } else if (p.peek_tok.kind == .lcbr || (p.peek_tok.kind == .lt && lit0_is_capital)) && (!p.inside_match || (p.inside_select && prev_tok_kind == .arrow && lit0_is_capital)) && !p.inside_match_case && (!p.inside_if || p.inside_select) && (!p.inside_for || p.inside_select) && !known_var { // && (p.tok.lit[0].is_capital() || p.builtin_mod) { // map.v has struct literal: map{field: expr} if p.peek_tok.kind == .lcbr && !(p.builtin_mod && p.file_base in ['map.v', 'map_d_gcboehm_opt.v']) && p.tok.lit == 'map' { // map{key_expr: val_expr} p.check(.name) p.check(.lcbr) map_init := p.map_init() p.check(.rcbr) return map_init } return p.struct_init(false) // short_syntax: false } else if p.peek_tok.kind == .lcbr && p.inside_if && lit0_is_capital && !known_var && language == .v { // if a == Foo{} {...} return p.struct_init(false) } else if p.peek_tok.kind == .dot && (lit0_is_capital && !known_var && language == .v) { // T.name if p.is_generic_name() { pos := p.tok.position() name := p.check_name() p.check(.dot) field := p.check_name() fkind := match field { 'name' { ast.GenericKindField.name } 'typ' { ast.GenericKindField.typ } else { ast.GenericKindField.unknown } } pos.extend(p.tok.position()) return ast.SelectorExpr{ expr: ast.Ident{ name: name scope: p.scope } field_name: field gkind_field: fkind pos: pos scope: p.scope } } // `Color.green` mut enum_name := p.check_name() enum_name_pos := p.prev_tok.position() if mod != '' { enum_name = mod + '.' + enum_name } else { enum_name = p.imported_symbols[enum_name] or { p.prepend_mod(enum_name) } } p.check(.dot) val := p.check_name() p.expr_mod = '' return ast.EnumVal{ enum_name: enum_name val: val pos: enum_name_pos.extend(p.prev_tok.position()) mod: mod } } else if language == .js && p.peek_tok.kind == .dot && p.peek_token(2).kind == .name { // JS. function call with more than 1 dot node = p.call_expr(language, mod) } else { if p.inside_in_array && ((lit0_is_capital && !known_var && language == .v) || (p.peek_tok.kind == .dot && p.peek_token(2).lit.len > 0 && p.peek_token(2).lit[0].is_capital()) || p.table.find_type_idx(p.mod + '.' + p.tok.lit) > 0) { type_pos := p.tok.position() typ := p.parse_type() return ast.TypeNode{ typ: typ pos: type_pos } } ident := p.parse_ident(language) node = ident if p.inside_defer { if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod) && ident.name != 'err' { p.defer_vars << ident } } } p.expr_mod = '' return node } fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr { // left == `a` in `a[0]` start_pos := p.tok.position() p.next() // [ mut has_low := true if p.tok.kind == .dotdot { has_low = false // [..end] p.next() mut high := ast.empty_expr() mut has_high := false if p.tok.kind != .rsbr { high = p.expr(0) has_high = true } pos := start_pos.extend(p.tok.position()) p.check(.rsbr) return ast.IndexExpr{ left: left pos: pos index: ast.RangeExpr{ low: ast.empty_expr() high: high has_high: has_high pos: pos is_gated: is_gated } is_gated: is_gated } } expr := p.expr(0) // `[expr]` or `[expr..` mut has_high := false if p.tok.kind == .dotdot { // [start..end] or [start..] p.next() mut high := ast.empty_expr() if p.tok.kind != .rsbr { has_high = true high = p.expr(0) } pos := start_pos.extend(p.tok.position()) p.check(.rsbr) return ast.IndexExpr{ left: left pos: pos index: ast.RangeExpr{ low: expr high: high has_high: has_high has_low: has_low pos: pos is_gated: is_gated } is_gated: is_gated } } // [expr] pos := start_pos.extend(p.tok.position()) p.check(.rsbr) mut or_kind := ast.OrKind.absent mut or_stmts := []ast.Stmt{} mut or_pos := token.Position{} if !p.or_is_handled { // a[i] or { ... } if p.tok.kind == .key_orelse { was_inside_or_expr := p.inside_or_expr p.inside_or_expr = true or_pos = p.tok.position() p.next() p.open_scope() or_stmts = p.parse_block_no_scope(false) or_pos = or_pos.extend(p.prev_tok.position()) p.close_scope() p.inside_or_expr = was_inside_or_expr return ast.IndexExpr{ left: left index: expr pos: pos or_expr: ast.OrExpr{ kind: .block stmts: or_stmts pos: or_pos } is_gated: is_gated } } // `a[i] ?` if p.tok.kind == .question { or_pos = p.tok.position() or_kind = .propagate p.next() } } return ast.IndexExpr{ left: left index: expr pos: pos or_expr: ast.OrExpr{ kind: or_kind stmts: or_stmts pos: or_pos } is_gated: is_gated } } fn (mut p Parser) scope_register_it() { p.scope.register(ast.Var{ name: 'it' pos: p.tok.position() is_used: true }) } fn (mut p Parser) scope_register_ab() { p.scope.register(ast.Var{ name: 'a' pos: p.tok.position() is_used: true }) p.scope.register(ast.Var{ name: 'b' pos: p.tok.position() is_used: true }) } fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr { p.next() if p.tok.kind == .dollar { return p.comptime_selector(left) } is_generic_call := p.is_generic_call() name_pos := p.tok.position() mut field_name := '' // check if the name is on the same line as the dot if (p.prev_tok.position().line_nr == name_pos.line_nr) || p.tok.kind != .name { field_name = p.check_name() } else { p.name_error = true } is_filter := field_name in ['filter', 'map', 'any', 'all'] if is_filter || field_name == 'sort' { p.open_scope() } // ! in mutable methods if p.tok.kind == .not && p.peek_tok.kind == .lpar { p.next() } // Method call // TODO move to fn.v call_expr() mut concrete_types := []ast.Type{} mut concrete_list_pos := p.tok.position() if is_generic_call { // `g.foo(10)` concrete_types = p.parse_concrete_types() concrete_list_pos = concrete_list_pos.extend(p.prev_tok.position()) // In case of `foo()` // T is unwrapped and registered in the checker. has_generic := concrete_types.any(it.has_flag(.generic)) if !has_generic { // will be added in checker p.table.register_fn_concrete_types(field_name, concrete_types) } } if p.tok.kind == .lpar { p.next() args := p.call_args() p.check(.rpar) mut or_stmts := []ast.Stmt{} mut or_kind := ast.OrKind.absent mut or_pos := p.tok.position() if p.tok.kind == .key_orelse { was_inside_or_expr := p.inside_or_expr p.inside_or_expr = true p.next() p.open_scope() p.scope.register(ast.Var{ name: 'err' typ: ast.error_type pos: p.tok.position() is_used: true is_stack_obj: true }) or_kind = .block or_stmts = p.parse_block_no_scope(false) or_pos = or_pos.extend(p.prev_tok.position()) p.close_scope() p.inside_or_expr = was_inside_or_expr } // `foo()?` if p.tok.kind == .question { p.next() or_kind = .propagate } // end_pos := p.prev_tok.position() pos := name_pos.extend(end_pos) comments := p.eat_comments(same_line: true) mcall_expr := ast.CallExpr{ left: left name: field_name args: args name_pos: name_pos pos: pos is_method: true concrete_types: concrete_types concrete_list_pos: concrete_list_pos or_block: ast.OrExpr{ stmts: or_stmts kind: or_kind pos: or_pos } scope: p.scope comments: comments } if is_filter || field_name == 'sort' { p.close_scope() } return mcall_expr } mut is_mut := false mut mut_pos := token.Position{} if p.inside_match || p.inside_if_expr { match left { ast.Ident, ast.SelectorExpr { is_mut = left.is_mut mut_pos = left.mut_pos } else {} } } pos := if p.name_error { left.position().extend(name_pos) } else { name_pos } sel_expr := ast.SelectorExpr{ expr: left field_name: field_name pos: pos is_mut: is_mut mut_pos: mut_pos scope: p.scope next_token: p.tok.kind } if is_filter { p.close_scope() } return sel_expr } fn (mut p Parser) parse_generic_types() ([]ast.Type, []string) { mut types := []ast.Type{} mut param_names := []string{} if p.tok.kind != .lt { return types, param_names } p.check(.lt) mut first_done := false mut count := 0 for p.tok.kind !in [.gt, .eof] { if first_done { p.check(.comma) } name := p.tok.lit if name.len > 0 && !name[0].is_capital() { p.error('generic parameter needs to be uppercase') } if name.len > 1 { p.error('generic parameter name needs to be exactly one char') } if !util.is_generic_type_name(p.tok.lit) { p.error('`$p.tok.lit` is a reserved name and cannot be used for generics') } if name in param_names { p.error('duplicated generic parameter `$name`') } if count > 8 { p.error('cannot have more than 9 generic parameters') } p.check(.name) param_names << name mut idx := p.table.find_type_idx(name) if idx == 0 { idx = p.table.register_type_symbol(ast.TypeSymbol{ name: name cname: util.no_dots(name) mod: p.mod kind: .any is_public: true }) } types << ast.new_type(idx).set_flag(.generic) first_done = true count++ } p.check(.gt) return types, param_names } fn (mut p Parser) parse_concrete_types() []ast.Type { mut types := []ast.Type{} if p.tok.kind != .lt { return types } p.next() // `<` mut first_done := false for p.tok.kind !in [.eof, .gt] { if first_done { p.check(.comma) } types << p.parse_type() first_done = true } p.check(.gt) // `>` return types } // is_generic_name returns true if the current token is a generic name. fn (p Parser) is_generic_name() bool { return p.tok.kind == .name && util.is_generic_type_name(p.tok.lit) } // `.green` // `pref.BuildMode.default_mode` fn (mut p Parser) enum_val() ast.EnumVal { start_pos := p.tok.position() p.check(.dot) val := p.check_name() return ast.EnumVal{ val: val pos: start_pos.extend(p.prev_tok.position()) } } fn (mut p Parser) filter_string_vet_errors(pos token.Position) { if p.vet_errors.len == 0 { return } p.vet_errors = p.vet_errors.filter( (it.typ == .trailing_space && it.pos.line_nr - 1 >= pos.last_line) || (it.typ != .trailing_space && it.pos.line_nr - 1 > pos.last_line) || (it.typ == .space_indent && it.pos.line_nr - 1 <= pos.line_nr) || (it.typ != .space_indent && it.pos.line_nr - 1 < pos.line_nr)) } fn (mut p Parser) string_expr() ast.Expr { is_raw := p.tok.kind == .name && p.tok.lit == 'r' is_cstr := p.tok.kind == .name && p.tok.lit == 'c' if is_raw || is_cstr { p.next() } mut node := ast.empty_expr() val := p.tok.lit mut pos := p.tok.position() pos.last_line = pos.line_nr + val.count('\n') if p.peek_tok.kind != .str_dollar { p.next() p.filter_string_vet_errors(pos) node = ast.StringLiteral{ val: val is_raw: is_raw language: if is_cstr { ast.Language.c } else { ast.Language.v } pos: pos } return node } mut exprs := []ast.Expr{} mut vals := []string{} mut has_fmts := []bool{} mut fwidths := []int{} mut precisions := []int{} mut visible_pluss := []bool{} mut fills := []bool{} mut fmts := []byte{} mut fposs := []token.Position{} // Handle $ interpolation p.inside_str_interp = true for p.tok.kind == .string { vals << p.tok.lit p.next() if p.tok.kind != .str_dollar { break } p.next() exprs << p.expr(0) mut has_fmt := false mut fwidth := 0 mut fwidthneg := false // 987698 is a magic default value, unlikely to be present in user input. NB: 0 is valid precision mut precision := 987698 mut visible_plus := false mut fill := false mut fmt := `_` // placeholder if p.tok.kind == .colon { p.next() // ${num:-2d} if p.tok.kind == .minus { fwidthneg = true p.next() } else if p.tok.kind == .plus { visible_plus = true p.next() } // ${num:2d} if p.tok.kind == .number { fields := p.tok.lit.split('.') if fields[0].len > 0 && fields[0][0] == `0` { fill = true } fwidth = fields[0].int() if fwidthneg { fwidth = -fwidth } if fields.len > 1 { precision = fields[1].int() } p.next() } if p.tok.kind == .name { if p.tok.lit.len == 1 { fmt = p.tok.lit[0] has_fmt = true p.next() } else { return p.error('format specifier may only be one letter') } } } fwidths << fwidth has_fmts << has_fmt precisions << precision visible_pluss << visible_plus fmts << fmt fills << fill fposs << p.prev_tok.position() } pos = pos.extend(p.prev_tok.position()) p.filter_string_vet_errors(pos) node = ast.StringInterLiteral{ vals: vals exprs: exprs need_fmts: has_fmts fwidths: fwidths precisions: precisions pluss: visible_pluss fills: fills fmts: fmts fmt_poss: fposs pos: pos } // need_fmts: prelimery - until checker finds out if really needed p.inside_str_interp = false return node } fn (mut p Parser) parse_number_literal() ast.Expr { mut pos := p.tok.position() is_neg := p.tok.kind == .minus if is_neg { p.next() pos = pos.extend(p.tok.position()) } lit := p.tok.lit full_lit := if is_neg { '-' + lit } else { lit } mut node := ast.empty_expr() if lit.index_any('.eE') >= 0 && lit[..2] !in ['0x', '0X', '0o', '0O', '0b', '0B'] { node = ast.FloatLiteral{ val: full_lit pos: pos } } else { node = ast.IntegerLiteral{ val: full_lit pos: pos } } p.next() return node } fn (mut p Parser) module_decl() ast.Module { mut module_attrs := []ast.Attr{} mut attrs_pos := p.tok.position() if p.tok.kind == .lsbr { p.attributes() module_attrs = p.attrs } mut name := 'main' is_skipped := p.tok.kind != .key_module mut module_pos := token.Position{} mut name_pos := token.Position{} mut mod_node := ast.Module{} if !is_skipped { p.attrs = [] module_pos = p.tok.position() p.next() name_pos = p.tok.position() name = p.check_name() mod_node = ast.Module{ pos: module_pos } if module_pos.line_nr != name_pos.line_nr { p.error_with_pos('`module` and `$name` must be at same line', name_pos) return mod_node } // NB: this shouldn't be reassigned into name_pos // as it creates a wrong position when extended // to module_pos n_pos := p.tok.position() if module_pos.line_nr == n_pos.line_nr && p.tok.kind != .comment && p.tok.kind != .eof { if p.tok.kind == .name { p.error_with_pos('`module $name`, you can only declare one module, unexpected `$p.tok.lit`', n_pos) return mod_node } else { p.error_with_pos('`module $name`, unexpected `$p.tok.kind` after module name', n_pos) return mod_node } } module_pos = attrs_pos.extend(name_pos) } full_name := util.qualify_module(p.pref, name, p.file_name) p.mod = full_name p.builtin_mod = p.mod == 'builtin' mod_node = ast.Module{ name: full_name short_name: name attrs: module_attrs is_skipped: is_skipped pos: module_pos name_pos: name_pos } if !is_skipped { for ma in module_attrs { match ma.name { 'manualfree' { p.is_manualfree = true } 'generated' { p.is_generated = true } 'has_globals' { if p.inside_vlib_file { p.has_globals = true } else { p.error_with_pos('[has_globals] is allowed only in .v files of `vlib` modules', ma.pos) } } else { p.error_with_pos('unknown module attribute `[$ma.name]`', ma.pos) return mod_node } } } } return mod_node } fn (mut p Parser) import_stmt() ast.Import { import_pos := p.tok.position() p.check(.key_import) mut pos := p.tok.position() mut import_node := ast.Import{ pos: import_pos.extend(pos) } if p.tok.kind == .lpar { p.error_with_pos('`import()` has been deprecated, use `import x` instead', pos) return import_node } mut mod_name_arr := []string{} mod_name_arr << p.check_name() if import_pos.line_nr != pos.line_nr { p.error_with_pos('`import` statements must be a single line', pos) return import_node } mut mod_alias := mod_name_arr[0] import_node = ast.Import{ pos: import_pos.extend(pos) mod_pos: pos alias_pos: pos } for p.tok.kind == .dot { p.next() submod_pos := p.tok.position() if p.tok.kind != .name { p.error_with_pos('module syntax error, please use `x.y.z`', submod_pos) return import_node } if import_pos.line_nr != submod_pos.line_nr { p.error_with_pos('`import` and `submodule` must be at same line', submod_pos) return import_node } submod_name := p.check_name() mod_name_arr << submod_name mod_alias = submod_name pos = pos.extend(submod_pos) import_node = ast.Import{ pos: import_pos.extend(pos) mod_pos: pos alias_pos: submod_pos mod: util.qualify_import(p.pref, mod_name_arr.join('.'), p.file_name) alias: mod_alias } } if mod_name_arr.len == 1 { import_node = ast.Import{ pos: import_node.pos mod_pos: import_node.mod_pos alias_pos: import_node.alias_pos mod: util.qualify_import(p.pref, mod_name_arr[0], p.file_name) alias: mod_alias } } mod_name := import_node.mod if p.tok.kind == .key_as { p.next() alias_pos := p.tok.position() mod_alias = p.check_name() if mod_alias == mod_name_arr.last() { p.error_with_pos('import alias `$mod_name as $mod_alias` is redundant', p.prev_tok.position()) return import_node } import_node = ast.Import{ pos: import_node.pos.extend(alias_pos) mod_pos: import_node.mod_pos alias_pos: alias_pos mod: import_node.mod alias: mod_alias } } if p.tok.kind == .lcbr { // import module { fn1, Type2 } syntax mut initial_syms_pos := p.tok.position() p.import_syms(mut import_node) initial_syms_pos = initial_syms_pos.extend(p.tok.position()) import_node = ast.Import{ ...import_node syms_pos: initial_syms_pos pos: import_node.pos.extend(initial_syms_pos) } p.register_used_import(mod_alias) // no `unused import` msg for parent } pos_t := p.tok.position() if import_pos.line_nr == pos_t.line_nr { if p.tok.kind !in [.lcbr, .eof, .comment] { p.error_with_pos('cannot import multiple modules at a time', pos_t) return import_node } } import_node.comments = p.eat_comments(same_line: true) import_node.next_comments = p.eat_comments(follow_up: true) p.imports[mod_alias] = mod_name // if mod_name !in p.table.imports { p.table.imports << mod_name p.ast_imports << import_node // } return import_node } // import_syms parses the inner part of `import module { submod1, submod2 }` fn (mut p Parser) import_syms(mut parent ast.Import) { p.next() pos_t := p.tok.position() if p.tok.kind == .rcbr { // closed too early p.error_with_pos('empty `$parent.mod` import set, remove `{}`', pos_t) return } if p.tok.kind != .name { // not a valid inner name p.error_with_pos('import syntax error, please specify a valid fn or type name', pos_t) return } for p.tok.kind == .name { pos := p.tok.position() alias := p.check_name() p.imported_symbols[alias] = parent.mod + '.' + alias // so we can work with this in fmt+checker parent.syms << ast.ImportSymbol{ pos: pos name: alias } if p.tok.kind == .comma { // go again if more than one p.next() continue } if p.tok.kind == .rcbr { // finish if closing `}` is seen break } } if p.tok.kind != .rcbr { p.error_with_pos('import syntax error, no closing `}`', p.tok.position()) return } p.next() } fn (mut p Parser) const_decl() ast.ConstDecl { p.top_level_statement_start() mut attrs := []ast.Attr{} if p.attrs.len > 0 { attrs = p.attrs p.attrs = [] } mut is_markused := false for ga in attrs { match ga.name { 'markused' { is_markused = true } else {} } } start_pos := p.tok.position() is_pub := p.tok.kind == .key_pub if is_pub { p.next() } const_pos := p.tok.position() p.check(.key_const) is_block := p.tok.kind == .lpar if is_block { p.next() // ( } mut fields := []ast.ConstField{} mut comments := []ast.Comment{} for { comments = p.eat_comments() if is_block && p.tok.kind == .eof { p.error('unexpected eof, expecting ´)´') return ast.ConstDecl{} } if p.tok.kind == .rpar { break } pos := p.tok.position() name := p.check_name() if util.contains_capital(name) { p.warn_with_pos('const names cannot contain uppercase letters, use snake_case instead', pos) } full_name := p.prepend_mod(name) p.check(.assign) if p.tok.kind == .key_fn { p.error('const initializer fn literal is not a constant') return ast.ConstDecl{} } if p.tok.kind == .eof { p.error('unexpected eof, expecting an expression') return ast.ConstDecl{} } expr := p.expr(0) field := ast.ConstField{ name: full_name mod: p.mod is_pub: is_pub expr: expr pos: pos.extend(expr.position()) comments: comments is_markused: is_markused } fields << field p.table.global_scope.register(field) comments = [] if !is_block { break } } p.top_level_statement_end() if is_block { p.check(.rpar) } return ast.ConstDecl{ pos: start_pos.extend_with_last_line(const_pos, p.prev_tok.line_nr) fields: fields is_pub: is_pub end_comments: comments is_block: is_block attrs: attrs } } fn (mut p Parser) return_stmt() ast.Return { first_pos := p.tok.position() p.next() // no return mut comments := p.eat_comments() if p.tok.kind == .rcbr || (p.tok.kind == .name && p.peek_tok.kind == .colon) { return ast.Return{ comments: comments pos: first_pos } } // return exprs exprs, comments2 := p.expr_list() comments << comments2 end_pos := exprs.last().position() return ast.Return{ exprs: exprs comments: comments pos: first_pos.extend(end_pos) } } // left hand side of `=` or `:=` in `a,b,c := 1,2,3` fn (mut p Parser) global_decl() ast.GlobalDecl { mut attrs := []ast.Attr{} if p.attrs.len > 0 { attrs = p.attrs p.attrs = [] } mut is_markused := false for ga in attrs { match ga.name { 'markused' { is_markused = true } else {} } } if !p.has_globals && !p.pref.enable_globals && !p.pref.is_fmt && !p.pref.translated && !p.pref.is_livemain && !p.pref.building_v && !p.builtin_mod { p.error('use `v -enable-globals ...` to enable globals') return ast.GlobalDecl{} } start_pos := p.tok.position() p.check(.key_global) is_block := p.tok.kind == .lpar if is_block { p.next() // ( } mut fields := []ast.GlobalField{} mut comments := []ast.Comment{} for { comments = p.eat_comments() if is_block && p.tok.kind == .eof { p.error('unexpected eof, expecting ´)´') return ast.GlobalDecl{} } if p.tok.kind == .rpar { break } pos := p.tok.position() name := p.check_name() has_expr := p.tok.kind == .assign mut expr := ast.empty_expr() mut typ := ast.void_type mut typ_pos := token.Position{} if has_expr { p.next() // = expr = p.expr(0) match expr { ast.CastExpr { typ = (expr as ast.CastExpr).typ } ast.StructInit { typ = (expr as ast.StructInit).typ } ast.ArrayInit { typ = (expr as ast.ArrayInit).typ } ast.ChanInit { typ = (expr as ast.ChanInit).typ } ast.BoolLiteral, ast.IsRefType { typ = ast.bool_type } ast.CharLiteral { typ = ast.char_type } ast.FloatLiteral { typ = ast.f64_type } ast.IntegerLiteral, ast.SizeOf { typ = ast.int_type } ast.StringLiteral, ast.StringInterLiteral { typ = ast.string_type } else { // type will be deduced by checker } } } else { typ_pos = p.tok.position() typ = p.parse_type() } field := ast.GlobalField{ name: name has_expr: has_expr expr: expr pos: pos typ_pos: typ_pos typ: typ comments: comments is_markused: is_markused } fields << field p.table.global_scope.register(field) comments = [] if !is_block { break } } if is_block { p.check(.rpar) } return ast.GlobalDecl{ pos: start_pos.extend(p.prev_tok.position()) mod: p.mod fields: fields end_comments: comments is_block: is_block attrs: attrs } } fn (mut p Parser) enum_decl() ast.EnumDecl { p.top_level_statement_start() is_pub := p.tok.kind == .key_pub start_pos := p.tok.position() if is_pub { p.next() } p.check(.key_enum) end_pos := p.tok.position() enum_name := p.check_name() if enum_name.len == 1 { p.error_with_pos('single letter capital names are reserved for generic template types.', end_pos) return ast.EnumDecl{} } if enum_name in p.imported_symbols { p.error_with_pos('cannot register enum `$enum_name`, this type was already imported', end_pos) return ast.EnumDecl{} } name := p.prepend_mod(enum_name) p.check(.lcbr) enum_decl_comments := p.eat_comments() mut vals := []string{} // mut default_exprs := []ast.Expr{} mut fields := []ast.EnumField{} for p.tok.kind != .eof && p.tok.kind != .rcbr { pos := p.tok.position() val := p.check_name() vals << val mut expr := ast.empty_expr() mut has_expr := false // p.warn('enum val $val') if p.tok.kind == .assign { p.next() expr = p.expr(0) has_expr = true } fields << ast.EnumField{ name: val pos: pos expr: expr has_expr: has_expr comments: p.eat_comments(same_line: true) next_comments: p.eat_comments() } } p.top_level_statement_end() p.check(.rcbr) is_flag := p.attrs.contains('flag') is_multi_allowed := p.attrs.contains('_allow_multiple_values') if is_flag { if fields.len > 32 { p.error('when an enum is used as bit field, it must have a max of 32 fields') return ast.EnumDecl{} } for f in fields { if f.has_expr { p.error_with_pos('when an enum is used as a bit field, you can not assign custom values', f.pos) return ast.EnumDecl{} } } pubfn := if p.mod == 'main' { 'fn' } else { 'pub fn' } p.codegen(' // [inline] $pubfn ( e &$enum_name) is_empty() bool { return int(*e) == 0 } [inline] $pubfn ( e &$enum_name) has(flag $enum_name) bool { return (int(*e) & (int(flag))) != 0 } [inline] $pubfn ( e &$enum_name) all(flag $enum_name) bool { return (int(*e) & (int(flag))) == int(flag) } [inline] $pubfn (mut e $enum_name) set(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) | (int(flag))) } } [inline] $pubfn (mut e $enum_name) clear(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) & ~(int(flag))) } } [inline] $pubfn (mut e $enum_name) toggle(flag $enum_name) { unsafe{ *e = ${enum_name}(int(*e) ^ (int(flag))) } } // ') } idx := p.table.register_type_symbol(ast.TypeSymbol{ kind: .enum_ name: name cname: util.no_dots(name) mod: p.mod info: ast.Enum{ vals: vals is_flag: is_flag is_multi_allowed: is_multi_allowed } is_public: is_pub }) if idx == -1 { p.error_with_pos('cannot register enum `$name`, another type with this name exists', end_pos) } enum_decl := ast.EnumDecl{ name: name is_pub: is_pub is_flag: is_flag is_multi_allowed: is_multi_allowed fields: fields pos: start_pos.extend_with_last_line(end_pos, p.prev_tok.line_nr) attrs: p.attrs comments: enum_decl_comments } p.table.register_enum_decl(enum_decl) return enum_decl } fn (mut p Parser) type_decl() ast.TypeDecl { start_pos := p.tok.position() is_pub := p.tok.kind == .key_pub if is_pub { p.next() } p.check(.key_type) end_pos := p.tok.position() decl_pos := start_pos.extend(end_pos) name_pos := p.tok.position() name := p.check_name() if name.len == 1 && name[0].is_capital() { p.error_with_pos('single letter capital names are reserved for generic template types.', decl_pos) return ast.FnTypeDecl{} } if name in p.imported_symbols { p.error_with_pos('cannot register alias `$name`, this type was already imported', end_pos) return ast.AliasTypeDecl{} } mut sum_variants := []ast.TypeNode{} generic_types, _ := p.parse_generic_types() decl_pos_with_generics := decl_pos.extend(p.prev_tok.position()) p.check(.assign) mut type_pos := p.tok.position() mut comments := []ast.Comment{} if p.tok.kind == .key_fn { // function type: `type mycallback = fn(string, int)` fn_name := p.prepend_mod(name) fn_type := p.parse_fn_type(fn_name) p.table.sym(fn_type).is_public = is_pub type_pos = type_pos.extend(p.tok.position()) comments = p.eat_comments(same_line: true) return ast.FnTypeDecl{ name: fn_name is_pub: is_pub typ: fn_type pos: decl_pos type_pos: type_pos comments: comments } } sum_variants << p.parse_sum_type_variants() // type SumType = A | B | c if sum_variants.len > 1 { for variant in sum_variants { variant_sym := p.table.sym(variant.typ) // TODO: implement this check for error too if variant_sym.kind == .none_ { p.error_with_pos('named sum type cannot have none as its variant', variant.pos) return ast.AliasTypeDecl{} } } variant_types := sum_variants.map(it.typ) prepend_mod_name := p.prepend_mod(name) typ := p.table.register_type_symbol(ast.TypeSymbol{ kind: .sum_type name: prepend_mod_name cname: util.no_dots(prepend_mod_name) mod: p.mod info: ast.SumType{ variants: variant_types is_generic: generic_types.len > 0 generic_types: generic_types } is_public: is_pub }) if typ == -1 { p.error_with_pos('cannot register sum type `$name`, another type with this name exists', name_pos) return ast.SumTypeDecl{} } comments = p.eat_comments(same_line: true) return ast.SumTypeDecl{ name: name typ: typ is_pub: is_pub variants: sum_variants generic_types: generic_types pos: decl_pos comments: comments } } // type MyType = int if generic_types.len > 0 { p.error_with_pos('generic type aliases are not yet implemented', decl_pos_with_generics) return ast.AliasTypeDecl{} } // sum_variants will have only one element parent_type := sum_variants[0].typ parent_sym := p.table.sym(parent_type) pidx := parent_type.idx() p.check_for_impure_v(parent_sym.language, decl_pos) prepend_mod_name := p.prepend_mod(name) idx := p.table.register_type_symbol(ast.TypeSymbol{ kind: .alias name: prepend_mod_name cname: util.no_dots(prepend_mod_name) mod: p.mod parent_idx: pidx info: ast.Alias{ parent_type: parent_type language: parent_sym.language } is_public: is_pub }) type_end_pos := p.prev_tok.position() if idx == -1 { p.error_with_pos('cannot register alias `$name`, another type with this name exists', name_pos) return ast.AliasTypeDecl{} } if idx == pidx { type_alias_pos := sum_variants[0].pos p.error_with_pos('a type alias can not refer to itself: $name', decl_pos.extend(type_alias_pos)) return ast.AliasTypeDecl{} } comments = p.eat_comments(same_line: true) return ast.AliasTypeDecl{ name: name is_pub: is_pub parent_type: parent_type type_pos: type_pos.extend(type_end_pos) pos: decl_pos comments: comments } } fn (mut p Parser) assoc() ast.Assoc { var_name := p.check_name() pos := p.tok.position() mut v := p.scope.find_var(var_name) or { p.error('unknown variable `$var_name`') return ast.Assoc{ scope: 0 } } v.is_used = true mut fields := []string{} mut vals := []ast.Expr{} p.check(.pipe) for p.tok.kind != .eof { fields << p.check_name() p.check(.colon) expr := p.expr(0) vals << expr if p.tok.kind == .comma { p.next() } if p.tok.kind == .rcbr { break } } return ast.Assoc{ var_name: var_name fields: fields exprs: vals pos: pos scope: p.scope } } fn (p &Parser) new_true_expr() ast.Expr { return ast.BoolLiteral{ val: true pos: p.tok.position() } } [noreturn] fn verror(s string) { util.verror('parser error', s) } fn (mut p Parser) top_level_statement_start() { if p.comments_mode == .toplevel_comments { p.scanner.set_is_inside_toplevel_statement(true) p.rewind_scanner_to_current_token_in_new_mode() $if debugscanner ? { eprintln('>> p.top_level_statement_start | tidx:${p.tok.tidx:-5} | p.tok.kind: ${p.tok.kind:-10} | p.tok.lit: $p.tok.lit $p.peek_tok.lit ${p.peek_token(2).lit} ${p.peek_token(3).lit} ...') } } } fn (mut p Parser) top_level_statement_end() { if p.comments_mode == .toplevel_comments { p.scanner.set_is_inside_toplevel_statement(false) p.rewind_scanner_to_current_token_in_new_mode() $if debugscanner ? { eprintln('>> p.top_level_statement_end | tidx:${p.tok.tidx:-5} | p.tok.kind: ${p.tok.kind:-10} | p.tok.lit: $p.tok.lit $p.peek_tok.lit ${p.peek_token(2).lit} ${p.peek_token(3).lit} ...') } } } fn (mut p Parser) rewind_scanner_to_current_token_in_new_mode() { // Go back and rescan some tokens, ensuring that the parser's // lookahead buffer p.peek_tok .. p.peek_token(3), will now contain // the correct tokens (possible comments), for the new mode // This refilling of the lookahead buffer is needed for the // .toplevel_comments parsing mode. tidx := p.tok.tidx p.scanner.set_current_tidx(tidx - 5) no_token := token.Token{} p.prev_tok = no_token p.tok = no_token p.peek_tok = no_token // requires 2 calls p.next() or check p.tok.kind != token.Kind.unknown p.next() for { p.next() // eprintln('rewinding to ${p.tok.tidx:5} | goal: ${tidx:5}') if tidx == p.tok.tidx { break } } } pub fn (mut p Parser) mark_var_as_used(varname string) bool { if obj := p.scope.find(varname) { match mut obj { ast.Var { obj.is_used = true return true } // ast.ConstField { // return true //} else {} } } return false } fn (mut p Parser) unsafe_stmt() ast.Stmt { mut pos := p.tok.position() p.next() if p.tok.kind != .lcbr { return p.error_with_pos('please use `unsafe {`', p.tok.position()) } p.next() if p.inside_unsafe { return p.error_with_pos('already inside `unsafe` block', pos) } if p.tok.kind == .rcbr { // `unsafe {}` pos.update_last_line(p.tok.line_nr) p.next() return ast.Block{ is_unsafe: true pos: pos } } p.inside_unsafe = true p.open_scope() // needed in case of `unsafe {stmt}` defer { p.inside_unsafe = false p.close_scope() } stmt := p.stmt(false) if p.tok.kind == .rcbr { if stmt is ast.ExprStmt { // `unsafe {expr}` if stmt.expr.is_expr() { p.next() pos.update_last_line(p.prev_tok.line_nr) ue := ast.UnsafeExpr{ expr: stmt.expr pos: pos } // parse e.g. `unsafe {expr}.foo()` expr := p.expr_with_left(ue, 0, p.is_stmt_ident) return ast.ExprStmt{ expr: expr pos: pos } } } } // unsafe {stmts} mut stmts := [stmt] for p.tok.kind != .rcbr { stmts << p.stmt(false) } p.next() pos.update_last_line(p.tok.line_nr) return ast.Block{ stmts: stmts is_unsafe: true pos: pos } } fn (mut p Parser) trace(fbase string, message string) { if p.file_base == fbase { println('> p.trace | ${fbase:-10s} | $message') } }