v2 / vlib / v / parser / parser.v
3588 lines · 3458 sloc · 96.05 KB · 476486007164a36917687b6c6d195faba990c93f
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module parser
5
6import v.scanner
7import v.ast
8import v.token
9import v.pref
10import v.util
11import v.errors
12import os
13import hash.fnv1a
14import strings
15
16@[minify]
17pub struct Parser {
18pub:
19 pref &pref.Preferences = unsafe { nil }
20mut:
21 file_base string // "hello.v"
22 file_path string // "/home/user/hello.v"
23 file_idx i16 // file idx in the global table `filelist`
24 file_display_path string // just "hello.v", when your current folder for the compilation is "/home/user/", otherwise the full path "/home/user/hello.v"
25 unique_prefix string // a hash of p.file_path, used for making anon fn generation unique
26 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.
27 // see comment in parse_file
28 tok token.Token
29 prev_tok token.Token
30 peek_tok token.Token
31 language ast.Language
32 fn_language ast.Language // .c for `fn C.abcd()` declarations
33 struct_language ast.Language // for `struct C.abcd{ embedded struct/union }` declarations
34 expr_level int // prevent too deep recursions for pathological programs
35 inside_vlib_file bool // true for all vlib/ files
36 inside_test_file bool // when inside _test.v or _test.vv file
37 inside_if bool
38 inside_comptime_if bool
39 inside_if_expr bool
40 inside_if_cond bool
41 inside_ct_if_expr bool
42 inside_or_expr bool
43 inside_for bool
44 inside_for_expr bool
45 inside_fn bool // true even with implicit main
46 inside_fn_return bool
47 inside_fn_param bool // true while parsing function parameter types
48 inside_fn_concrete_type bool // parsing fn_name[concrete_type]() call expr
49 inside_call_args bool // true inside f( .... )
50 inside_unsafe_fn bool
51 inside_str_interp bool
52 inside_array_lit bool
53 inside_array_init_type_expr bool // parsing `[]typeof(expr){}` element type expression
54 inside_in_array bool
55 inside_infix bool
56 inside_assign_rhs bool // rhs assignment
57 inside_match bool // to separate `match A { }` from `Struct{}`
58 inside_select bool // to allow `ch <- Struct{} {` inside `select`
59 inside_match_case bool // to separate `match_expr { }` from `Struct{}`
60 inside_match_body bool // to fix eval not used TODO
61 inside_ct_match bool
62 inside_ct_match_case bool
63 inside_ct_match_body bool
64 inside_unsafe bool
65 inside_sum_type bool // to prevent parsing inline sum type again
66 inside_asm_template bool
67 inside_asm bool
68 inside_defer bool
69 defer_mode ast.DeferMode
70 inside_generic_params bool // indicates if parsing between `<` and `>` of a method/function
71 inside_receiver_param bool // indicates if parsing the receiver parameter inside the first `(` and `)` of a method
72 inside_struct_field_decl bool
73 inside_struct_attr_decl bool
74 inside_map_init bool
75 inside_orm bool
76 inside_chan_decl bool
77 inside_attr_decl bool
78 inside_lock_exprs bool
79 array_dim int // array dim parsing level
80 fixed_array_dim int // fixed array dim parsing level
81 allow_auto_fixed_array_size bool // allow `[..]` while parsing fixed array literal types
82 or_is_handled bool // ignore `or` in this expression
83 builtin_mod bool // are we in the `builtin` module?
84 mod string // current module name
85 is_manualfree bool // true when `@[manualfree] module abc`, makes *all* fns in the current .v file, opt out of autofree
86 has_globals bool // `@[has_globals] module abc` - allow globals declarations, even without -enable-globals, in that single .v file __only__
87 is_generated bool // `@[generated] module abc` - turn off compiler notices for that single .v file __only__.
88 is_translated bool // `@[translated] module abc` - mark a file as translated, to relax some compiler checks for translated code.
89 attrs []ast.Attr // attributes before next decl stmt
90 expr_mod string // for constructing full type names in parse_type()
91 last_enum_name string // saves the last enum name on an array initialization
92 last_enum_mod string // saves the last enum mod name on an array initialization
93 imports map[string]string // alias => mod_name
94 ast_imports []ast.Import // mod_names
95 used_imports []string
96 auto_imports []string // imports, the user does not need to specify
97 implied_imports []string // ​imports that the user's code uses but omitted to import explicitly, used by `vfmt`
98 imported_symbols map[string]string
99 imported_symbols_used map[string]bool
100 imported_symbols_trie token.KeywordsMatcherTrie
101 is_amp bool // for generating the right code for `&Foo{}`
102 returns bool
103 is_stmt_ident bool // true while the beginning of a statement is an ident/selector
104 expecting_type bool // `is Type`, expecting type
105 expecting_value bool = true // true where a node value will be used
106 cur_fn_name string
107 cur_fn_scope &ast.Scope = unsafe { nil }
108 label_names []string
109 name_error bool // indicates if the token is not a name or the name is on another line
110 n_asm int // controls assembly labels
111 global_labels []string
112 comptime_if_cond bool
113 defer_vars []ast.Ident
114 should_abort bool // when too many errors/warnings/notices are accumulated, should_abort becomes true, and the parser should stop
115 codegen_text string
116 anon_struct_decl ast.StructDecl
117 init_generic_types []ast.Type
118 consume_init_generic_types bool
119 if_cond_comments []ast.Comment
120 left_comments []ast.Comment
121 script_mode bool
122 script_mode_start_token token.Token
123 pending_top_stmts []ast.Stmt
124 generic_type_level int // to avoid infinite recursion segfaults due to compiler bugs in ensure_type_exists
125 main_already_defined bool // TODO move to checker
126 is_vls bool
127 is_vls_skip_file bool // in `vls` mode, skip parse and check for unrelated files, such as `vlib`
128 inside_import_section bool
129 cur_comments []ast.Comment // comments between other stmts
130pub mut:
131 scanner &scanner.Scanner = unsafe { nil }
132 table &ast.Table = unsafe { nil }
133 scope &ast.Scope = unsafe { nil }
134
135 opened_scopes int
136 max_opened_scopes int = 100 // values above 300 risk stack overflow
137
138 errors []errors.Error
139 warnings []errors.Warning
140 notices []errors.Notice
141 template_paths []string // record all compiled $tmpl files; needed for `v watch run webserver.v`
142 template_line_map []ast.TemplateLineInfo // line mapping for current template compilation
143 content ParseContentKind
144}
145
146enum ParseContentKind {
147 file
148 text
149 stmt
150 comptime
151}
152
153// for tests
154pub fn parse_stmt(text string, mut table ast.Table, mut scope ast.Scope) ast.Stmt {
155 $if trace_parse_stmt ? {
156 eprintln('> ${@MOD}.${@FN} text: ${text}')
157 }
158 mut p := Parser{
159 content: .stmt
160 scanner: scanner.new_scanner(text, .skip_comments, &pref.Preferences{})
161 inside_test_file: true
162 table: table
163 pref: &pref.Preferences{}
164 scope: scope
165 }
166 p.init_parse_fns()
167 util.timing_start('PARSE stmt')
168 defer {
169 util.timing_measure_cumulative('PARSE stmt')
170 }
171 p.read_first_token()
172 return p.stmt(false)
173}
174
175pub fn parse_comptime(tmpl_path string, text string, mut table ast.Table, pref_ &pref.Preferences, mut scope ast.Scope) &ast.File {
176 $if trace_parse_comptime ? {
177 eprintln('> ${@MOD}.${@FN} text: ${text}')
178 }
179 pref_copy := *pref_
180 comptime_pref := &pref.Preferences{
181 ...pref_copy
182 output_mode: .silent
183 }
184 mut p := Parser{
185 content: .comptime
186 file_path: tmpl_path
187 scanner: scanner.new_scanner(text, .skip_comments, comptime_pref)
188 table: table
189 pref: comptime_pref
190 scope: scope
191 errors: []errors.Error{}
192 warnings: []errors.Warning{}
193 }
194 mut res := p.parse()
195 unsafe { p.free_scanner() }
196 res.is_template_text = true
197 return res
198}
199
200pub fn parse_text(text string, path string, mut table ast.Table, comments_mode scanner.CommentsMode, pref_ &pref.Preferences) &ast.File {
201 $if trace_parse_text ? {
202 eprintln('> ${@MOD}.${@FN} comments_mode: ${comments_mode:-20} | path: ${path:-20} | text: ${text}')
203 }
204 mut p := Parser{
205 content: .text
206 scanner: scanner.new_scanner(text, comments_mode, pref_)
207 table: table
208 pref: pref_
209 is_vls: pref_.is_vls
210 is_vls_skip_file: pref_.is_vls && path != pref_.path
211 scope: &ast.Scope{
212 start_pos: 0
213 parent: table.global_scope
214 }
215 errors: []errors.Error{}
216 warnings: []errors.Warning{}
217 }
218 p.set_path(path)
219 mut res := p.parse()
220 unsafe { p.free_scanner() }
221 res.is_parse_text = true
222 return res
223}
224
225@[unsafe]
226pub fn (mut p Parser) free() {
227 unsafe { p.free_scanner() }
228}
229
230@[unsafe]
231fn (mut p Parser) free_scanner() {
232 unsafe {
233 if p.scanner != 0 {
234 p.scanner.free()
235 p.scanner = &scanner.Scanner(nil)
236 }
237 }
238}
239
240const normalised_working_folder = (os.real_path(os.getwd()) + os.path_separator).replace('\\', '/')
241
242pub fn (mut p Parser) set_path(path string) {
243 p.file_path = path
244 p.file_base = os.base(path)
245 p.file_display_path =
246 os.real_path(p.file_path).replace_once(normalised_working_folder, '').replace('\\', '/')
247 p.inside_vlib_file = os.dir(path).contains('vlib')
248 p.inside_test_file = p.file_base.ends_with('_test.v') || p.file_base.ends_with('_test.vv')
249 || p.file_base.all_before_last('.v').all_before_last('.').ends_with('_test')
250
251 hash := fnv1a.sum64_string(path)
252 p.unique_prefix = hash.hex_full()
253
254 p.file_backend_mode = .v
255 before_dot_v := path.all_before_last('.v') // also works for .vv and .vsh
256 language := before_dot_v.all_after_last('.')
257 language_with_underscore := before_dot_v.all_after_last('_')
258 if language == before_dot_v && language_with_underscore == before_dot_v {
259 return
260 }
261 actual_language := if language == before_dot_v { language_with_underscore } else { language }
262 match actual_language {
263 'c' {
264 p.file_backend_mode = .c
265 }
266 'js' {
267 p.file_backend_mode = .js
268 }
269 else {
270 arch := pref.arch_from_string(actual_language) or { pref.Arch._auto }
271 p.file_backend_mode = ast.pref_arch_to_table_language(arch)
272 if arch == ._auto {
273 p.file_backend_mode = .v
274 }
275 }
276 }
277}
278
279fn should_skip_vls_file(pref_ &pref.Preferences, path string) bool {
280 if !pref_.is_vls {
281 return false
282 }
283 if pref_.line_info != '' {
284 project_dir := if os.is_dir(pref_.path) {
285 os.real_path(pref_.path)
286 } else {
287 os.real_path(os.dir(pref_.linfo.path))
288 }
289 return !os.real_path(path).starts_with(project_dir)
290 }
291 return path != pref_.path
292}
293
294pub fn parse_file(path string, mut table ast.Table, comments_mode scanner.CommentsMode, pref_ &pref.Preferences) &ast.File {
295 // Note: when comments_mode == .toplevel_comments,
296 // the parser gives feedback to the scanner about toplevel statements, so that the scanner can skip
297 // all the tricky inner comments. This is needed because we do not have a good general solution
298 // for handling them, and should be removed when we do (the general solution is also needed for vfmt)
299 $if trace_parse_file ? {
300 eprintln('> ${@MOD}.${@FN} comments_mode: ${comments_mode:-20} | path: ${path}')
301 }
302 mut file_idx := i16(table.filelist.index(path))
303 if file_idx == -1 {
304 file_idx = i16(table.filelist.len)
305 table.filelist << path
306 }
307 mut p := Parser{
308 content: .file
309 scanner: scanner.new_scanner_file(path, file_idx, comments_mode, pref_) or { panic(err) }
310 table: table
311 pref: pref_
312 // Only set vls mode if it's the file the user requested via `v -vls-mode file.v`
313 // Otherwise we'd be parsing entire stdlib in vls mode
314 is_vls: pref_.is_vls && path == pref_.path
315 is_vls_skip_file: should_skip_vls_file(pref_, path)
316 scope: &ast.Scope{
317 start_pos: 0
318 parent: table.global_scope
319 }
320 errors: []errors.Error{}
321 warnings: []errors.Warning{}
322 file_idx: file_idx
323 }
324 p.set_path(path)
325 res := p.parse()
326 unsafe { p.free_scanner() }
327 return res
328}
329
330pub fn (mut p Parser) parse() &ast.File {
331 $if trace_parse ? {
332 eprintln('> ${@FILE}:${@LINE} | p.path: ${p.file_path} | content: ${p.content} | nr_tokens: ${p.scanner.all_tokens.len} | nr_lines: ${p.scanner.line_nr} | nr_bytes: ${p.scanner.text.len}')
333 }
334 util.timing_start('PARSE')
335 defer {
336 util.timing_measure_cumulative('PARSE')
337 }
338 // comments_mode: comments_mode
339 p.init_parse_fns()
340 p.read_first_token()
341 mut stmts := []ast.Stmt{}
342 for p.tok.kind == .comment {
343 stmts << p.comment_stmt()
344 }
345 // module
346 module_decl := p.module_decl()
347 if module_decl.is_skipped {
348 stmts.insert(0, ast.Stmt(module_decl))
349 } else {
350 stmts << module_decl
351 }
352 p.inside_import_section = true
353 // imports
354 for {
355 if p.tok.kind == .key_import {
356 stmts << p.import_stmt()
357 continue
358 }
359 if p.tok.kind == .comment {
360 stmts << p.comment_stmt()
361 continue
362 }
363 break
364 }
365 for {
366 if p.tok.kind == .eof && p.pending_top_stmts.len == 0 {
367 // Imported module files are discovered after the initial parse pass,
368 // so unused import warnings are emitted later by the builder.
369 break
370 }
371 stmt := if p.pending_top_stmts.len > 0 {
372 pending := p.pending_top_stmts[0]
373 p.pending_top_stmts.delete(0)
374 pending
375 } else {
376 p.top_stmt()
377 }
378 // clear the attributes after each statement
379 if !(stmt is ast.ExprStmt && stmt.expr is ast.Comment) {
380 p.attrs = []
381 }
382 stmts << stmt
383 if p.should_abort {
384 break
385 }
386 }
387 p.scope.end_pos = p.tok.pos
388
389 mut errors_ := p.errors.clone()
390 mut warnings := p.warnings.clone()
391 mut notices := p.notices.clone()
392
393 if p.pref.check_only {
394 errors_ << p.scanner.errors
395 warnings << p.scanner.warnings
396 notices << p.scanner.notices
397 }
398
399 if p.pref.is_check_overflow {
400 p.register_auto_import('builtin.overflow')
401 }
402 p.handle_codegen_for_file()
403
404 ast_file := &ast.File{
405 path: p.file_path
406 path_base: p.file_base
407 is_test: p.inside_test_file
408 is_generated: p.is_generated
409 is_translated: p.is_translated
410 language: p.file_backend_mode
411 nr_lines: p.scanner.line_nr
412 nr_bytes: p.scanner.text.len
413 nr_tokens: p.scanner.all_tokens.len
414 mod: module_decl
415 imports: p.ast_imports
416 imported_symbols: p.imported_symbols
417 imported_symbols_trie: token.new_keywords_matcher_from_array_trie(p.imported_symbols.keys())
418 imported_symbols_used: p.imported_symbols_used
419 auto_imports: p.auto_imports
420 used_imports: p.used_imports
421 implied_imports: p.implied_imports
422 stmts: stmts
423 scope: p.scope
424 global_scope: p.table.global_scope
425 errors: errors_
426 warnings: warnings
427 notices: notices
428 global_labels: p.global_labels
429 template_paths: p.template_paths
430 unique_prefix: p.unique_prefix
431 }
432 $if trace_parse_file_path_and_mod ? {
433 eprintln('>> ast.File, tokens: ${ast_file.nr_tokens:5}, mname: ${ast_file.mod.name:20}, sname: ${ast_file.mod.short_name:11}, path: ${p.file_display_path}')
434 }
435 return ast_file
436}
437
438pub fn parse_files(paths []string, mut table ast.Table, pref_ &pref.Preferences) []&ast.File {
439 mut timers := util.new_timers(should_print: false, label: 'parse_files: ${paths}')
440 $if time_parsing ? {
441 timers.should_print = true
442 }
443 stop_after_first_error := pref_.fatal_errors
444 || (pref_.output_mode == .stdout && !pref_.check_only && !pref_.is_vls)
445 unsafe {
446 mut files := []&ast.File{cap: paths.len}
447 for path in paths {
448 timers.start('parse_file ${path}')
449 file := parse_file(path, mut table, .skip_comments, pref_)
450 files << file
451 timers.show('parse_file ${path}')
452 if stop_after_first_error && file.errors.len > 0 {
453 break
454 }
455 }
456 handle_codegen_for_multiple_files(mut files)
457 return files
458 }
459}
460
461fn (mut p Parser) init_parse_fns() {
462 // p.prefix_parse_fns = make(100, 100, sizeof(PrefixParseFn))
463 // p.prefix_parse_fns[token.Kind.name] = parse_name
464}
465
466fn (mut p Parser) read_first_token() {
467 // need to call next() 2 times to get peek token and current token
468 p.next()
469 p.next()
470}
471
472@[inline]
473fn (p &Parser) peek_token(n int) token.Token {
474 return p.scanner.peek_token(n - 2)
475}
476
477// peek token in if guard `if x,y := opt()` after var_list `x,y`
478fn (p &Parser) peek_token_after_var_list() token.Token {
479 mut n := 0
480 mut tok := p.tok
481 for tok.kind != .eof {
482 if tok.kind == .key_mut {
483 n += 2
484 } else {
485 n++
486 }
487 tok = p.scanner.peek_token(n - 2)
488 if tok.kind != .comma {
489 break
490 } else {
491 n++
492 tok = p.scanner.peek_token(n - 2)
493 }
494 }
495 return tok
496}
497
498fn (mut p Parser) open_scope() {
499 if p.opened_scopes > p.max_opened_scopes {
500 p.should_abort = true
501 p.error('nested opened scopes limit reached: ${p.max_opened_scopes}')
502 return
503 }
504 p.scope = &ast.Scope{
505 parent: p.scope
506 start_pos: p.tok.pos
507 }
508 p.opened_scopes++
509}
510
511fn (mut p Parser) close_scope() {
512 // p.scope.end_pos = p.tok.pos
513 // NOTE: since this is usually called after `p.parse_block()`
514 // ie. when `prev_tok` is rcbr `}` we most likely want `prev_tok`
515 // we could do the following, but probably not needed in 99% of cases:
516 // `end_pos = if p.prev_tok.kind == .rcbr { p.prev_tok.pos } else { p.tok.pos }`
517 p.scope.end_pos = p.prev_tok.pos
518 p.scope.parent.children << p.scope
519 p.scope = p.scope.parent
520 p.opened_scopes--
521}
522
523fn (mut p Parser) parse_block() []ast.Stmt {
524 p.open_scope()
525 stmts := p.parse_block_no_scope(false)
526 p.close_scope()
527 return stmts
528}
529
530fn (mut p Parser) is_in_top_level_comptime(inside_assign_rhs bool) bool {
531 // TODO: find out a better way detect we are in top level.
532 return p.cur_fn_name.len == 0
533 && (p.inside_ct_if_expr || p.inside_ct_match || p.inside_ct_match_body)
534 && !inside_assign_rhs && !p.script_mode && p.tok.kind != .name
535}
536
537fn (mut p Parser) parse_block_no_scope(is_top_level bool) []ast.Stmt {
538 p.check(.lcbr)
539 mut stmts := []ast.Stmt{cap: 20}
540 old_assign_rhs := p.inside_assign_rhs
541 p.inside_assign_rhs = false
542 if p.tok.kind != .rcbr {
543 mut count := 0
544 for p.tok.kind !in [.eof, .rcbr] {
545 if p.is_in_top_level_comptime(old_assign_rhs) {
546 // top level `$if cond { println() }` should goto `p.stmt()`
547 stmts << p.top_stmt()
548 } else {
549 stmts << p.stmt(is_top_level)
550 }
551 count++
552 if count % 100000 == 0 {
553 if p.is_vls {
554 // Stuck in VLS mode, exit
555 return []
556 }
557 eprintln('parsed ${count} statements so far from fn ${p.cur_fn_name} ...')
558 }
559 if count > 1000000 {
560 p.error_with_pos('parsed over ${count} statements from fn ${p.cur_fn_name}, the parser is probably stuck',
561 p.tok.pos())
562 return []
563 }
564 }
565 }
566 p.inside_assign_rhs = old_assign_rhs
567 if is_top_level {
568 p.top_level_statement_end()
569 }
570 p.check(.rcbr)
571 // on assignment the last callexpr must be marked as return used recursively
572 if p.inside_assign_rhs && stmts.len > 0 {
573 mut last_stmt := stmts.last()
574 p.mark_last_call_return_as_used(mut last_stmt)
575 }
576 return stmts
577}
578
579fn (mut p Parser) mark_last_call_return_as_used(mut last_stmt ast.Stmt) {
580 match mut last_stmt {
581 ast.ExprStmt {
582 match mut last_stmt.expr {
583 ast.CallExpr {
584 // last stmt on block is CallExpr
585 last_stmt.expr.is_return_used = true
586 if last_stmt.expr.or_block.stmts.len > 0 {
587 mut or_block_last_stmt := last_stmt.expr.or_block.stmts.last()
588 p.mark_last_call_return_as_used(mut or_block_last_stmt)
589 }
590 }
591 ast.ConcatExpr {
592 // last stmt on block is: a, b, c := ret1(), ret2(), ret3()
593 for mut expr in last_stmt.expr.vals {
594 if mut expr is ast.CallExpr {
595 expr.is_return_used = true
596 }
597 }
598 }
599 ast.IfExpr {
600 // last stmt on block is: if .. { foo() } else { bar() }
601 for mut branch in last_stmt.expr.branches {
602 if branch.stmts.len > 0 {
603 mut last_if_stmt := branch.stmts.last()
604 p.mark_last_call_return_as_used(mut last_if_stmt)
605 }
606 }
607 }
608 ast.InfixExpr {
609 if last_stmt.expr.or_block.stmts.len > 0 {
610 mut or_block_last_stmt := last_stmt.expr.or_block.stmts.last()
611 p.mark_last_call_return_as_used(mut or_block_last_stmt)
612 }
613 // last stmt has infix expr with CallExpr: foo()? + 'a'
614 mut left_expr := last_stmt.expr.left
615 for {
616 mut next_left_expr := ast.Expr(ast.EmptyExpr{})
617 if mut left_expr is ast.InfixExpr {
618 if left_expr.or_block.stmts.len > 0 {
619 mut or_block_last_stmt := left_expr.or_block.stmts.last()
620 p.mark_last_call_return_as_used(mut or_block_last_stmt)
621 }
622 next_left_expr = left_expr.left
623 } else if mut left_expr is ast.CallExpr {
624 left_expr.is_return_used = true
625 if left_expr.or_block.stmts.len > 0 {
626 mut or_block_last_stmt := left_expr.or_block.stmts.last()
627 p.mark_last_call_return_as_used(mut or_block_last_stmt)
628 }
629 break
630 } else {
631 break
632 }
633 left_expr = next_left_expr
634 continue
635 }
636 }
637 ast.ComptimeCall, ast.ComptimeSelector, ast.PrefixExpr, ast.SelectorExpr {
638 if last_stmt.expr.or_block.stmts.len > 0 {
639 mut or_block_last_stmt := last_stmt.expr.or_block.stmts.last()
640 p.mark_last_call_return_as_used(mut or_block_last_stmt)
641 }
642 }
643 else {}
644 }
645 }
646 else {}
647 }
648}
649
650@[inline]
651fn (mut p Parser) next() {
652 p.prev_tok = p.tok
653 p.tok = p.peek_tok
654 p.peek_tok = p.scanner.scan()
655}
656
657fn (mut p Parser) check(expected token.Kind) {
658 p.name_error = false
659 if _likely_(p.tok.kind == expected) {
660 p.next()
661 } else {
662 if expected == .name {
663 p.name_error = true
664 }
665 mut s := expected.str()
666 // quote keywords, punctuation, operators
667 if token.is_key(s) || (s.len > 0 && !s[0].is_letter()) {
668 s = '`${s}`'
669 }
670 p.unexpected(expecting: s)
671 }
672}
673
674// recover_until_closing_rcbr skips the remaining contents of a `{ ... }` block after
675// the opening `{` has already been consumed, leaving the parser positioned after the
676// matching closing brace or at EOF.
677fn (mut p Parser) recover_until_closing_rcbr() {
678 mut brace_level := 1
679 for p.tok.kind != .eof && brace_level > 0 {
680 if p.tok.kind == .lcbr {
681 brace_level++
682 } else if p.tok.kind == .rcbr {
683 brace_level--
684 }
685 p.next()
686 }
687}
688
689// JS functions can have multiple dots in their name:
690// JS.foo.bar.and.a.lot.more.dots()
691fn (mut p Parser) check_js_name() string {
692 mut name := ''
693 for p.peek_tok.kind == .dot {
694 name += '${p.tok.lit}.'
695 p.next() // .name
696 p.next() // .dot
697 }
698 // last .name
699 name += p.tok.lit
700 p.next()
701 return name
702}
703
704@[direct_array_access]
705fn is_ident_name(name string) bool {
706 if name.len == 0 {
707 return false
708 }
709 if !util.name_char_table[name[0]] {
710 return false
711 }
712 for i in 1 .. name.len {
713 if !util.func_char_table[name[i]] {
714 return false
715 }
716 }
717 return true
718}
719
720fn (mut p Parser) check_name() string {
721 pos := p.tok.pos()
722 name := p.tok.lit
723 if p.tok.kind != .name && p.peek_tok.kind == .dot && name in p.imports {
724 p.register_used_import(name)
725 } else if p.tok.kind == .name && p.is_imported_symbol(name) && !p.imported_symbols_used[name] {
726 // symbols like Enum.field_name
727 p.register_used_import_for_symbol_name(p.imported_symbols[name])
728 }
729 if !is_ident_name(name) {
730 p.check(.name)
731 } else {
732 p.next()
733 }
734 if !p.inside_orm && !p.inside_attr_decl && name == 'sql' {
735 p.error_with_pos('unexpected keyword `sql`, expecting name', pos)
736 }
737 return name
738}
739
740@[if trace_parser ?]
741fn (p &Parser) trace_parser(label string) {
742 eprintln('parsing: ${p.file_path:-30}|tok.pos: ${p.tok.pos().line_str():-39}|tok.kind: ${p.tok.kind:-10}|tok.lit: ${p.tok.lit:-10}|${label}')
743}
744
745fn (mut p Parser) top_stmt() ast.Stmt {
746 p.trace_parser('top_stmt')
747 for {
748 mut keep_cur_comments := false
749 defer {
750 // clear `cur_comments` after each statement, except a comment stmt
751 if !keep_cur_comments && p.pref.is_vls {
752 p.cur_comments.clear()
753 }
754 }
755 if p.tok.kind !in [.key_import, .comment, .dollar] {
756 // import section should only prepend by `import`, `comment` or `$if`.
757 p.inside_import_section = false
758 }
759 match p.tok.kind {
760 .key_pub {
761 match p.peek_tok.kind {
762 .key_const {
763 return p.const_decl()
764 }
765 .key_fn {
766 return p.fn_decl()
767 }
768 .key_struct, .key_union {
769 return p.struct_decl(false)
770 }
771 .key_interface {
772 return p.interface_decl()
773 }
774 .key_enum {
775 return p.enum_decl()
776 }
777 .key_type {
778 return p.type_decl()
779 }
780 else {
781 return p.error('wrong pub keyword usage')
782 }
783 }
784 }
785 .at {
786 if p.peek_tok.kind == .lsbr {
787 p.attributes()
788 continue
789 } else {
790 return p.error('@[attr] expected')
791 }
792 }
793 .lsbr {
794 // attrs are stored in `p.attrs`
795 p.attributes()
796 continue
797 }
798 .key_interface {
799 return p.interface_decl()
800 }
801 .key_import {
802 if !p.inside_import_section {
803 p.error_with_pos('`import x` can only be declared at the beginning of the file',
804 p.tok.pos())
805 }
806 return p.import_stmt()
807 }
808 .key_global {
809 return p.global_decl()
810 }
811 .key_const {
812 return p.const_decl()
813 }
814 .key_fn {
815 return p.fn_decl()
816 }
817 .key_struct {
818 return p.struct_decl(false)
819 }
820 .dollar {
821 match p.peek_tok.kind {
822 .eof {
823 return p.unexpected(got: 'eof')
824 }
825 .key_for {
826 comptime_for_stmt := p.comptime_for()
827 return p.other_stmts(comptime_for_stmt)
828 }
829 .key_if {
830 if_expr := p.if_expr(true, false)
831 cur_stmt := ast.ExprStmt{
832 expr: if_expr
833 pos: if_expr.pos
834 }
835 if p.pref.is_fmt || comptime_if_expr_contains_top_stmt(if_expr) {
836 return cur_stmt
837 } else {
838 return p.other_stmts(cur_stmt)
839 }
840 }
841 .key_match {
842 mut pos := p.tok.pos()
843 expr := p.match_expr(true, false)
844 pos.update_last_line(p.prev_tok.line_nr)
845 return ast.ExprStmt{
846 expr: expr
847 pos: pos
848 }
849 }
850 .name {
851 // handles $dbg directly without registering token
852 if p.peek_tok.lit == 'dbg' {
853 return p.dbg_stmt()
854 } else {
855 mut pos := p.tok.pos()
856 expr := p.expr(0)
857 pos.update_last_line(p.prev_tok.line_nr)
858 return ast.ExprStmt{
859 expr: expr
860 pos: pos
861 }
862 }
863 }
864 else {
865 return p.unexpected()
866 }
867 }
868 }
869 .hash {
870 return p.hash()
871 }
872 .key_type {
873 return p.type_decl()
874 }
875 .key_enum {
876 return p.enum_decl()
877 }
878 .key_union {
879 return p.struct_decl(false)
880 }
881 .comment {
882 keep_cur_comments = true
883 return p.comment_stmt()
884 }
885 .semicolon {
886 return p.semicolon_stmt()
887 }
888 .key_asm {
889 return p.asm_stmt(true)
890 }
891 else {
892 return p.other_stmts(ast.empty_stmt)
893 }
894 }
895
896 // clear `cur_comments` after each statement, except a comment stmt
897 if !keep_cur_comments && p.pref.is_vls {
898 p.cur_comments.clear()
899 }
900 if p.should_abort {
901 break
902 }
903 }
904 // TODO: remove dummy return statement
905 // the compiler complains if it's not there
906 return ast.empty_stmt
907}
908
909fn comptime_if_expr_contains_top_stmt(if_expr ast.IfExpr) bool {
910 for branch in if_expr.branches {
911 for stmt in branch.stmts {
912 if stmt is ast.ExprStmt {
913 if stmt.expr is ast.IfExpr {
914 if !comptime_if_expr_contains_top_stmt(stmt.expr) {
915 return false
916 }
917 } else if stmt.expr is ast.CallExpr {
918 return false
919 }
920 } else if stmt is ast.AssignStmt {
921 return false
922 } else if stmt is ast.HashStmt {
923 return true
924 }
925 }
926 }
927 return true
928}
929
930fn (mut p Parser) other_stmts(cur_stmt ast.Stmt) ast.Stmt {
931 old_inside_fn := p.inside_fn
932 old_cur_fn_name := p.cur_fn_name
933 old_cur_fn_scope := p.cur_fn_scope
934 old_label_names := p.label_names.clone()
935 p.inside_fn = true
936 if p.pref.is_script && !p.pref.is_test {
937 p.script_mode = true
938 p.script_mode_start_token = p.tok
939
940 if p.main_already_defined {
941 p.error('function `main` is already defined, put your script statements inside it')
942 }
943
944 p.open_scope()
945 p.cur_fn_name = 'main.main'
946 p.cur_fn_scope = p.scope
947 main_scope := p.scope
948 mut top_stmts := []ast.Stmt{}
949 mut main_stmts := []ast.Stmt{}
950 if cur_stmt != ast.empty_stmt {
951 main_stmts << cur_stmt
952 }
953 for p.tok.kind != .eof {
954 stmt := p.stmt(false)
955 if stmt is ast.FnDecl {
956 top_stmts << stmt
957 continue
958 }
959 main_stmts << stmt
960 }
961 main_label_names := p.label_names.clone()
962 p.close_scope()
963
964 p.script_mode = false
965 p.inside_fn = old_inside_fn
966 p.cur_fn_name = old_cur_fn_name
967 p.cur_fn_scope = old_cur_fn_scope
968 p.label_names = old_label_names
969 main_fn := ast.FnDecl{
970 name: 'main.main'
971 short_name: 'main'
972 mod: 'main'
973 is_main: true
974 stmts: main_stmts
975 file: p.file_path
976 return_type: ast.void_type
977 scope: main_scope
978 label_names: main_label_names
979 }
980 if top_stmts.len == 0 {
981 return main_fn
982 }
983 p.pending_top_stmts << top_stmts
984 p.pending_top_stmts << ast.Stmt(main_fn)
985 first := p.pending_top_stmts[0]
986 p.pending_top_stmts.delete(0)
987 return first
988 } else if p.pref.is_fmt || p.pref.is_vet {
989 stmt := p.stmt(false)
990 p.inside_fn = old_inside_fn
991 p.cur_fn_name = old_cur_fn_name
992 p.cur_fn_scope = old_cur_fn_scope
993 p.label_names = old_label_names
994 return stmt
995 } else {
996 err := p.error('bad top level statement ' + p.tok.str())
997 p.inside_fn = old_inside_fn
998 p.cur_fn_name = old_cur_fn_name
999 p.cur_fn_scope = old_cur_fn_scope
1000 p.label_names = old_label_names
1001 return err
1002 }
1003}
1004
1005fn (p &Parser) relative_token(offset int) token.Token {
1006 return match offset {
1007 0 { p.tok }
1008 1 { p.peek_tok }
1009 else { p.peek_token(offset) }
1010 }
1011}
1012
1013fn (p &Parser) is_script_receiver_method_decl_start() bool {
1014 mut fn_offset := 0
1015 if p.tok.kind == .key_pub {
1016 if p.peek_tok.kind != .key_fn {
1017 return false
1018 }
1019 fn_offset = 1
1020 } else if p.tok.kind != .key_fn {
1021 return false
1022 }
1023 if p.relative_token(fn_offset + 1).kind != .lpar {
1024 return false
1025 }
1026 mut offset := fn_offset + 2
1027 mut paren_level := 1
1028 for paren_level > 0 {
1029 tok := p.relative_token(offset)
1030 match tok.kind {
1031 .lpar {
1032 paren_level++
1033 }
1034 .rpar {
1035 paren_level--
1036 }
1037 .eof {
1038 return false
1039 }
1040 else {}
1041 }
1042
1043 offset++
1044 }
1045 name_tok := p.relative_token(offset)
1046 next_after_name := p.relative_token(offset + 1)
1047 return name_tok.kind == .name && next_after_name.kind in [.lpar, .lsbr]
1048}
1049
1050fn (mut p Parser) script_fn_decl() ast.FnDecl {
1051 main_scope := p.scope
1052 file_scope := if main_scope.parent != unsafe { nil } { main_scope.parent } else { main_scope }
1053 old_script_mode := p.script_mode
1054 old_inside_fn := p.inside_fn
1055 old_cur_fn_name := p.cur_fn_name
1056 old_cur_fn_scope := p.cur_fn_scope
1057 old_label_names := p.label_names.clone()
1058 p.script_mode = false
1059 p.inside_fn = false
1060 p.cur_fn_name = ''
1061 p.cur_fn_scope = unsafe { nil }
1062 p.scope = file_scope
1063 defer {
1064 p.script_mode = old_script_mode
1065 p.inside_fn = old_inside_fn
1066 p.cur_fn_name = old_cur_fn_name
1067 p.cur_fn_scope = old_cur_fn_scope
1068 p.label_names = old_label_names
1069 p.scope = main_scope
1070 }
1071 return p.fn_decl()
1072}
1073
1074// TODO: [if vfmt]
1075fn (mut p Parser) check_comment() ast.Comment {
1076 if p.tok.kind == .comment {
1077 return p.comment()
1078 }
1079 return ast.Comment{}
1080}
1081
1082fn (mut p Parser) comment() ast.Comment {
1083 mut pos := p.tok.pos()
1084 text := p.tok.lit
1085 num_newlines := text.count('\n')
1086 is_multi := num_newlines > 0
1087 pos.last_line = pos.line_nr + num_newlines
1088 p.next()
1089 return ast.Comment{
1090 text: text
1091 is_multi: is_multi
1092 pos: pos
1093 }
1094}
1095
1096fn (mut p Parser) comment_stmt() ast.ExprStmt {
1097 comment := p.comment()
1098 if p.pref.is_vls {
1099 p.cur_comments << comment
1100 }
1101 return ast.ExprStmt{
1102 expr: comment
1103 pos: comment.pos
1104 }
1105}
1106
1107@[params]
1108struct EatCommentsConfig {
1109pub:
1110 same_line bool // Only eat comments on the same line as the previous token
1111 follow_up bool // Comments directly below the previous token as long as there is no empty line
1112}
1113
1114fn (mut p Parser) eat_comments(cfg EatCommentsConfig) []ast.Comment {
1115 mut line := p.prev_tok.line_nr + p.prev_tok.lit.count('\n')
1116 mut comments := []ast.Comment{}
1117 for {
1118 if p.tok.kind != .comment || (cfg.same_line && p.tok.line_nr > line)
1119 || (cfg.follow_up && p.tok.line_nr > line + 1) {
1120 break
1121 }
1122 comments << p.comment()
1123 if cfg.follow_up {
1124 line = p.prev_tok.line_nr + p.prev_tok.lit.count('\n')
1125 }
1126 }
1127 return comments
1128}
1129
1130fn (mut p Parser) goto_eof() {
1131 for p.tok.kind != .eof {
1132 p.next()
1133 }
1134}
1135
1136fn (mut p Parser) stmt(is_top_level bool) ast.Stmt {
1137 // ensure that possible parser aborts, are handled as early as possible (on the *next* processed statement):
1138 if p.should_abort {
1139 abort_pos := p.tok.pos()
1140 p.goto_eof()
1141 return ast.NodeError{
1142 idx: 0
1143 pos: abort_pos
1144 }
1145 }
1146
1147 mut keep_cur_comments := false
1148 defer {
1149 if !keep_cur_comments && p.pref.is_vls {
1150 p.cur_comments.clear()
1151 }
1152 }
1153
1154 p.trace_parser('stmt(${is_top_level})')
1155 p.is_stmt_ident = p.tok.kind == .name
1156 match p.tok.kind {
1157 .lcbr {
1158 mut pos := p.tok.pos()
1159 if p.peek_token(2).kind == .colon {
1160 expr := p.expr(0)
1161 // `{ 'abc' : 22 }`
1162 return ast.ExprStmt{
1163 expr: expr
1164 pos: pos
1165 }
1166 } else {
1167 stmts := p.parse_block()
1168 pos.update_last_line(p.prev_tok.line_nr)
1169 return ast.Block{
1170 stmts: stmts
1171 scope: p.scope.children.last()
1172 pos: pos
1173 }
1174 }
1175 }
1176 .at, .lsbr {
1177 is_stmt_attr := if p.tok.kind == .at {
1178 p.peek_tok.kind == .lsbr
1179 } else {
1180 p.is_attributes()
1181 }
1182 if p.script_mode && is_stmt_attr {
1183 attr_pos := p.tok.pos()
1184 p.attributes()
1185 if token.is_decl(p.tok.kind) {
1186 stmt := p.stmt(is_top_level)
1187 p.attrs = []
1188 return stmt
1189 }
1190 p.attrs = []
1191 return p.error_with_pos('attributes can only be used before declarations', attr_pos)
1192 }
1193 return p.parse_multi_expr(is_top_level)
1194 }
1195 .name {
1196 if p.peek_tok.kind == .name && p.tok.lit == 'sql' {
1197 return p.sql_stmt_or_expr()
1198 }
1199 if p.peek_tok.kind == .colon {
1200 // `label:`
1201 spos := p.tok.pos()
1202 name := p.check_name()
1203 if name in p.label_names {
1204 return p.error_with_pos('duplicate label `${name}`', spos)
1205 }
1206 p.label_names << name
1207 p.next()
1208 if p.tok.kind == .key_for {
1209 for_pos := p.tok.pos()
1210 mut stmt := p.stmt(is_top_level)
1211 match mut stmt {
1212 ast.ForStmt {
1213 stmt.label = name
1214 return stmt
1215 }
1216 ast.ForInStmt {
1217 stmt.label = name
1218 return stmt
1219 }
1220 ast.ForCStmt {
1221 stmt.label = name
1222 return stmt
1223 }
1224 else {
1225 return p.error_with_pos('unknown kind of For statement', for_pos)
1226 }
1227 }
1228 }
1229 return ast.GotoLabel{
1230 name: name
1231 pos: spos.extend(p.tok.pos())
1232 }
1233 } else if p.peek_tok.kind == .name {
1234 if p.is_vls {
1235 // So that a line with a simple `var_name` works
1236 p.next()
1237 return ast.ExprStmt{
1238 expr: p.ident(.v)
1239 }
1240 }
1241 return p.unexpected(got: 'name `${p.tok.lit}`')
1242 } else if !p.inside_if_expr && !p.inside_match_body && !p.inside_or_expr
1243 && p.peek_tok.kind in [.rcbr, .eof] && !p.scope.mark_var_as_used(p.tok.lit) {
1244 return p.error_with_pos('`${p.tok.lit}` evaluated but not used', p.tok.pos())
1245 }
1246 return p.parse_multi_expr(is_top_level)
1247 }
1248 .key_for {
1249 return p.for_stmt()
1250 }
1251 .comment {
1252 keep_cur_comments = true
1253 return p.comment_stmt()
1254 }
1255 .key_return {
1256 if !p.inside_defer {
1257 return p.return_stmt()
1258 } else {
1259 return p.error_with_pos('`return` not allowed inside `defer` block', p.tok.pos())
1260 }
1261 }
1262 .dollar {
1263 match p.peek_tok.kind {
1264 .key_if {
1265 mut pos := p.tok.pos()
1266 expr := p.if_expr(true, false)
1267 pos.update_last_line(p.prev_tok.line_nr)
1268 return ast.ExprStmt{
1269 expr: expr
1270 pos: pos
1271 }
1272 }
1273 .key_for {
1274 return p.comptime_for()
1275 }
1276 .key_match {
1277 mut pos := p.tok.pos()
1278 expr := p.match_expr(true, false)
1279 pos.update_last_line(p.prev_tok.line_nr)
1280 return ast.ExprStmt{
1281 expr: expr
1282 pos: pos
1283 }
1284 }
1285 .name {
1286 // handles $dbg directly without registering token
1287 if p.peek_tok.lit == 'dbg' {
1288 return p.dbg_stmt()
1289 } else {
1290 mut pos := p.tok.pos()
1291 expr := p.expr(0)
1292 pos.update_last_line(p.prev_tok.line_nr)
1293 return ast.ExprStmt{
1294 expr: expr
1295 pos: pos
1296 }
1297 }
1298 }
1299 else {
1300 return p.unexpected(got: '\$')
1301 }
1302 }
1303 }
1304 .key_continue, .key_break {
1305 tok := p.tok
1306 line := p.tok.line_nr
1307 p.next()
1308 mut label := ''
1309 if p.tok.line_nr == line && p.tok.kind == .name {
1310 label = p.check_name()
1311 }
1312 return ast.BranchStmt{
1313 kind: tok.kind
1314 label: label
1315 scope: p.scope
1316 pos: tok.pos()
1317 }
1318 }
1319 .key_unsafe {
1320 return p.unsafe_stmt()
1321 }
1322 .hash {
1323 return p.hash()
1324 }
1325 .key_assert {
1326 p.next()
1327 mut pos := p.tok.pos()
1328 expr := p.expr(0)
1329 pos.update_last_line(p.prev_tok.line_nr)
1330 mut extra := ast.empty_expr
1331 mut extra_pos := p.tok.pos()
1332 if p.tok.kind == .comma {
1333 p.next()
1334 extra_pos = p.tok.pos()
1335 extra = p.expr(0)
1336 // dump(extra)
1337 extra_pos = extra_pos.extend(p.tok.pos())
1338 } else if p.tok.line_nr == p.prev_tok.line_nr + p.prev_tok.lit.count('\n')
1339 && p.tok.kind !in [.comment, .semicolon, .rcbr, .eof, .key_return, .key_break, .key_continue] {
1340 line_nr := p.tok.line_nr
1341 err := p.unexpected(got: p.tok.str(), expecting: '`,`')
1342 for p.tok.kind != .eof && p.tok.line_nr == line_nr {
1343 p.next()
1344 }
1345 return err
1346 }
1347 return ast.AssertStmt{
1348 expr: expr
1349 extra: extra
1350 extra_pos: extra_pos
1351 pos: pos.extend(p.tok.pos())
1352 is_used: p.inside_test_file || !p.pref.is_prod
1353 }
1354 }
1355 .key_defer {
1356 if !p.inside_defer {
1357 spos := p.tok.pos()
1358 p.next()
1359 mut defer_mode := ast.DeferMode.scoped
1360 if p.tok.kind == .lpar {
1361 p.next()
1362 mode_pos := p.tok.pos()
1363 mode := p.check_name()
1364 match mode {
1365 'fn' {
1366 defer_mode = .function
1367 }
1368 else {
1369 return p.error_with_pos('unknown `defer` mode: `${mode}`', mode_pos)
1370 }
1371 }
1372
1373 p.check(.rpar)
1374 }
1375 p.inside_defer = true
1376 p.defer_mode = defer_mode
1377 p.defer_vars = []ast.Ident{}
1378 stmts := p.parse_block()
1379 p.inside_defer = false
1380 return ast.DeferStmt{
1381 mode: defer_mode
1382 scope: p.scope
1383 stmts: stmts
1384 defer_vars: p.defer_vars.clone()
1385 pos: spos.extend_with_last_line(p.prev_tok.pos(), p.prev_tok.line_nr)
1386 }
1387 } else {
1388 return p.error_with_pos('`defer` blocks cannot be nested', p.tok.pos())
1389 }
1390 }
1391 .key_go, .key_spawn {
1392 if (p.pref.use_coroutines || p.pref.is_fmt) && p.tok.kind == .key_go {
1393 go_expr := p.go_expr()
1394 return ast.ExprStmt{
1395 expr: go_expr
1396 pos: go_expr.pos
1397 }
1398 } else {
1399 spawn_expr := p.spawn_expr()
1400 return ast.ExprStmt{
1401 expr: spawn_expr
1402 pos: spawn_expr.pos
1403 }
1404 }
1405 }
1406 .key_goto {
1407 p.next()
1408 spos := p.tok.pos()
1409 name := p.check_name()
1410 return ast.GotoStmt{
1411 name: name
1412 pos: spos
1413 }
1414 }
1415 .key_pub {
1416 if p.script_mode && p.is_script_receiver_method_decl_start() {
1417 return p.script_fn_decl()
1418 }
1419 return p.parse_multi_expr(is_top_level)
1420 }
1421 .key_fn {
1422 if p.script_mode && p.is_script_receiver_method_decl_start() {
1423 return p.script_fn_decl()
1424 }
1425 return p.parse_multi_expr(is_top_level)
1426 }
1427 .key_const {
1428 return p.error_with_pos('const can only be defined at the top level (outside of functions)',
1429 p.tok.pos())
1430 }
1431 .key_asm {
1432 return p.asm_stmt(false)
1433 }
1434 .semicolon {
1435 return p.semicolon_stmt()
1436 }
1437 // Allow struct definitions inside functions
1438 .key_struct, .key_union {
1439 return p.struct_decl(false)
1440 }
1441 // literals, 'if', etc. in here
1442 else {
1443 return p.parse_multi_expr(is_top_level)
1444 }
1445 }
1446}
1447
1448fn (mut p Parser) dbg_stmt() ast.DebuggerStmt {
1449 pos := p.tok.pos()
1450 p.check(.dollar)
1451 p.check(.name)
1452 p.register_auto_import('v.debug')
1453 return ast.DebuggerStmt{
1454 pos: pos
1455 }
1456}
1457
1458fn (mut p Parser) semicolon_stmt() ast.SemicolonStmt {
1459 pos := p.tok.pos()
1460 p.check(.semicolon)
1461 return ast.SemicolonStmt{
1462 pos: pos
1463 }
1464}
1465
1466fn (mut p Parser) expr_list(expect_value bool) []ast.Expr {
1467 mut exprs := []ast.Expr{cap: 1}
1468 for {
1469 expr := if expect_value { p.expr(0) } else { p.expr_no_value(0) }
1470 if expr !is ast.Comment {
1471 exprs << expr
1472 if p.tok.kind != .comma {
1473 break
1474 }
1475 p.next()
1476 }
1477 }
1478 return exprs
1479}
1480
1481@[direct_array_access]
1482fn (mut p Parser) parse_multi_expr(is_top_level bool) ast.Stmt {
1483 // in here might be 1) multi-expr 2) multi-assign
1484 // 1, a, c ... } // multi-expression
1485 // a, mut b ... :=/= // multi-assign
1486 // collect things upto hard boundaries
1487 tok := p.tok
1488 mut pos := tok.pos()
1489
1490 mut defer_vars := p.defer_vars.clone()
1491 p.defer_vars = []ast.Ident{}
1492
1493 left := p.expr_list(p.inside_assign_rhs)
1494
1495 if !(p.inside_defer && p.defer_mode == .function && p.tok.kind == .decl_assign) {
1496 defer_vars << p.defer_vars
1497 }
1498 p.defer_vars = defer_vars
1499
1500 left0 := left[0]
1501 if tok.kind in [.key_mut, .key_shared, .key_atomic] && left0.is_blank_ident() {
1502 return p.error_with_pos('cannot use `${tok.kind}` on `_`', tok.pos())
1503 }
1504
1505 if tok.kind == .key_mut && p.tok.kind != .decl_assign {
1506 return p.error('expecting `:=` (e.g. `mut x :=`)')
1507 }
1508 // TODO: remove translated
1509 if p.tok.kind.is_assign() {
1510 return p.partial_assign_stmt(left)
1511 } else if !p.pref.translated && !p.is_translated && !p.pref.is_fmt && !p.pref.is_vet
1512 && tok.kind !in [.key_if, .key_match, .key_lock, .key_rlock, .key_select] {
1513 for node in left {
1514 if (is_top_level || p.tok.kind !in [.comment, .rcbr])
1515 && node !in [ast.CallExpr, ast.PostfixExpr, ast.ComptimeCall, ast.SelectorExpr, ast.DumpExpr] {
1516 is_complex_infix_expr := node is ast.InfixExpr
1517 && node.op in [.left_shift, .right_shift, .unsigned_right_shift, .arrow]
1518 if !is_complex_infix_expr && !p.is_vls {
1519 return p.error_with_pos('expression evaluated but not used', node.pos())
1520 }
1521 }
1522 }
1523 }
1524 pos.update_last_line(p.prev_tok.line_nr)
1525 if left.len == 1 {
1526 return ast.ExprStmt{
1527 expr: left0
1528 pos: left0.pos()
1529 is_expr: p.inside_for
1530 }
1531 }
1532 return ast.ExprStmt{
1533 expr: ast.ConcatExpr{
1534 vals: left
1535 pos: tok.pos()
1536 }
1537 pos: pos
1538 }
1539}
1540
1541fn (mut p Parser) ident(language ast.Language) ast.Ident {
1542 is_option := p.tok.kind == .question && p.peek_tok.kind == .lsbr
1543 if is_option {
1544 p.next()
1545 }
1546 is_shared := p.tok.kind == .key_shared
1547 is_atomic := p.tok.kind == .key_atomic
1548 if is_shared {
1549 p.register_auto_import('sync')
1550 }
1551 mut_pos := p.tok.pos()
1552 modifier_kind := p.tok.kind
1553 is_mut := p.tok.kind == .key_mut || is_shared || is_atomic
1554 if is_mut {
1555 p.next()
1556 }
1557 is_static := p.tok.kind == .key_static
1558 if is_static {
1559 p.next()
1560 }
1561 is_volatile := p.tok.kind == .key_volatile
1562 if is_volatile {
1563 p.next()
1564 }
1565 if p.tok.kind !in [.name, .key_type] {
1566 if is_mut || is_static || is_volatile {
1567 p.error_with_pos('the `${modifier_kind}` keyword is invalid here', mut_pos)
1568 } else {
1569 p.unexpected(got: 'token `${p.tok.lit}`')
1570 }
1571 return ast.Ident{
1572 scope: p.scope
1573 }
1574 }
1575 in_select := p.prev_tok.kind == .arrow
1576 pos := p.tok.pos()
1577 mut name := p.check_name()
1578 if name == '_' {
1579 return ast.Ident{
1580 tok_kind: p.tok.kind
1581 name: '_'
1582 comptime: p.comptime_if_cond
1583 kind: .blank_ident
1584 pos: pos
1585 info: ast.IdentVar{
1586 is_option: is_option
1587 }
1588 scope: p.scope
1589 }
1590 }
1591 is_following_concrete_types := p.is_following_concrete_types()
1592 mut concrete_types := []ast.Type{}
1593 if p.expr_mod.len > 0 {
1594 name = '${p.expr_mod}.${name}'
1595 }
1596
1597 // parsers ident like var?, except on '<- var' '$if ident ?', '[if define ?]'
1598 allowed_cases := !in_select && !p.inside_comptime_if && !p.inside_ct_if_expr
1599 mut or_kind := ast.OrKind.absent
1600 mut or_stmts := []ast.Stmt{}
1601 mut or_pos := token.Pos{}
1602 mut or_scope := ast.empty_scope
1603
1604 if allowed_cases && p.tok.kind == .question && p.peek_tok.kind != .lpar { // var?, not var?(
1605 or_kind = ast.OrKind.propagate_option
1606 or_scope = p.scope
1607 p.check(.question)
1608 } else if allowed_cases && p.tok.kind == .key_orelse {
1609 or_kind = ast.OrKind.block
1610 or_stmts, or_pos, or_scope = p.or_block(.no_err_var)
1611 } else if is_following_concrete_types {
1612 // `generic_fn[int]`
1613 concrete_types = p.parse_concrete_types()
1614 }
1615 typ := match p.peek_tok.kind {
1616 .string {
1617 ast.string_type_idx
1618 }
1619 .lsbr {
1620 ast.array_type_idx
1621 }
1622 else {
1623 if p.tok.kind == .dot {
1624 obj := p.scope.find_ptr(name)
1625 if obj != unsafe { nil } {
1626 match obj {
1627 ast.Var { obj.typ }
1628 else { 0 }
1629 }
1630 } else {
1631 0
1632 }
1633 } else {
1634 0
1635 }
1636 }
1637 }
1638
1639 return ast.Ident{
1640 tok_kind: p.tok.kind
1641 kind: .unresolved
1642 name: name
1643 comptime: p.comptime_if_cond
1644 language: language
1645 mod: p.mod
1646 pos: pos
1647 is_mut: is_mut
1648 mut_pos: mut_pos
1649 info: ast.IdentVar{
1650 typ: typ
1651 is_mut: is_mut
1652 is_static: is_static
1653 is_volatile: is_volatile
1654 is_option: or_kind != ast.OrKind.absent
1655 share: ast.sharetype_from_flags(is_shared, is_atomic)
1656 }
1657 scope: p.scope
1658 or_expr: ast.OrExpr{
1659 kind: or_kind
1660 stmts: or_stmts
1661 pos: or_pos
1662 scope: or_scope
1663 }
1664 concrete_types: concrete_types
1665 }
1666}
1667
1668fn (mut p Parser) alias_array_type() ast.Type {
1669 full_name := p.prepend_mod(p.tok.lit)
1670
1671 if idx := p.table.type_idxs[full_name] {
1672 if idx == 0 {
1673 return ast.void_type
1674 }
1675 sym := p.table.sym(ast.idx_to_type(idx))
1676 if sym.info is ast.Alias {
1677 if sym.info.parent_type == 0 {
1678 return ast.void_type
1679 }
1680 if p.table.sym(sym.info.parent_type).kind == .array {
1681 return idx
1682 }
1683 }
1684 }
1685 return ast.void_type
1686}
1687
1688fn (p &Parser) is_known_non_placeholder_type(name string) bool {
1689 if idx := p.table.type_idxs[name] {
1690 return p.table.sym(ast.idx_to_type(idx)).kind != .placeholder
1691 }
1692 return false
1693}
1694
1695@[direct_array_access]
1696fn (mut p Parser) name_expr() ast.Expr {
1697 prev_tok_kind := p.prev_tok.kind
1698 mut node := ast.empty_expr
1699
1700 if p.expecting_type {
1701 if p.tok.kind == .dollar {
1702 node = p.parse_comptime_type()
1703 p.expecting_type = false
1704 return node
1705 }
1706 p.expecting_type = false
1707 // get type position before moving to next
1708 is_known_var := p.scope.known_var(p.tok.lit)
1709 if is_known_var {
1710 p.scope.mark_var_as_used(p.tok.lit)
1711 return p.ident(.v)
1712 } else {
1713 type_pos := p.tok.pos()
1714 typ := p.parse_type()
1715 return ast.TypeNode{
1716 typ: typ
1717 pos: type_pos
1718 }
1719 }
1720 }
1721 language := match p.tok.lit {
1722 'C' { ast.Language.c }
1723 'JS' { ast.Language.js }
1724 'WASM' { ast.Language.wasm }
1725 else { ast.Language.v }
1726 }
1727
1728 if language != .v {
1729 p.check_for_impure_v(language, p.tok.pos())
1730 }
1731 is_option := p.tok.kind == .question
1732 if is_option {
1733 if p.peek_tok.kind in [.name, .lsbr] {
1734 p.check(.question)
1735 }
1736 }
1737 is_array := p.tok.kind == .lsbr
1738 is_fixed_array := is_array && p.peek_tok.kind == .number
1739 mut mod := ''
1740 // p.warn('resetting')
1741 p.expr_mod = ''
1742 // `map[string]int` initialization
1743 if p.peek_tok.kind == .lsbr && p.tok.lit == 'map' {
1744 mut pos := p.tok.pos()
1745 mut map_type := p.parse_map_type()
1746 if p.tok.kind == .lcbr {
1747 p.next()
1748 if p.tok.kind == .rcbr {
1749 pos = pos.extend(p.tok.pos())
1750 p.next()
1751 } else {
1752 p.error('`}` expected; explicit `map` initialization does not support parameters')
1753 }
1754 }
1755 if is_option {
1756 map_type = map_type.set_flag(.option)
1757 }
1758 node = ast.MapInit{
1759 typ: map_type
1760 pos: pos
1761 }
1762 if p.tok.kind == .lpar {
1763 // ?map[int]int(none) cast expr
1764 p.check(.lpar)
1765 expr := p.expr(0)
1766 p.check(.rpar)
1767 return ast.CastExpr{
1768 typ: map_type
1769 typname: p.table.sym(map_type).name
1770 expr: expr
1771 pos: pos.extend(p.tok.pos())
1772 }
1773 }
1774 return node
1775 }
1776 // `chan typ{...}`
1777 if p.tok.lit == 'chan' {
1778 first_pos := p.tok.pos()
1779 mut last_pos := first_pos
1780 mut elem_type_pos := p.peek_tok.pos()
1781 if p.peek_tok.kind == .not {
1782 return p.error_with_pos('cannot use chan with Result type', p.peek_tok.pos())
1783 }
1784 chan_type := p.parse_chan_type()
1785 elem_type_pos = elem_type_pos.extend(p.prev_tok.pos())
1786 mut has_cap := false
1787 mut cap_expr := ast.empty_expr
1788 p.check(.lcbr)
1789 if p.tok.kind == .rcbr {
1790 last_pos = p.tok.pos()
1791 p.next()
1792 } else {
1793 key := p.check_name()
1794 p.check(.colon)
1795 match key {
1796 'cap' {
1797 has_cap = true
1798 cap_expr = p.expr(0)
1799 }
1800 'len', 'init' {
1801 return p.error('`${key}` cannot be initialized for `chan`. Did you mean `cap`?')
1802 }
1803 else {
1804 return p.error('wrong field `${key}`, expecting `cap`')
1805 }
1806 }
1807
1808 last_pos = p.tok.pos()
1809 p.check(.rcbr)
1810 }
1811 if chan_type == ast.chan_type {
1812 p.error_with_pos('`chan` has no type specified. Use `chan Type{}` instead of `chan{}`',
1813 first_pos.extend(last_pos))
1814 }
1815 return ast.ChanInit{
1816 pos: first_pos.extend(last_pos)
1817 elem_type_pos: elem_type_pos
1818 has_cap: has_cap
1819 cap_expr: cap_expr
1820 typ: chan_type
1821 }
1822 }
1823 // Raw string (`s := r'hello \n ')
1824 if p.peek_tok.kind == .string && p.tok.line_nr == p.peek_tok.line_nr && !p.inside_str_interp
1825 && p.peek_token(2).kind != .colon {
1826 if p.tok.kind == .name && p.tok.lit in ['r', 'c', 'js'] {
1827 return p.string_expr()
1828 } else {
1829 // don't allow any other string prefix except `r`, `js` and `c`
1830 return p.error('only `c`, `r`, `js` are recognized string prefixes, but you tried to use `${p.tok.lit}`')
1831 }
1832 }
1833 // don't allow r`byte` and c`byte`
1834 if p.peek_tok.kind == .chartoken && p.tok.lit.len == 1 && p.tok.lit[0] in [`r`, `c`] {
1835 opt := if p.tok.lit == 'r' { '`r` (raw string)' } else { '`c` (c string)' }
1836 return p.error('cannot use ${opt} with `byte` and `rune`')
1837 }
1838 // Make sure that the var is not marked as used in assignments: `x = 1`, `x += 2` etc
1839 // but only when it's actually used (e.g. `println(x)`)
1840 known_var := if p.peek_tok.kind.is_assign() {
1841 p.scope.known_var(p.tok.lit)
1842 } else {
1843 p.scope.mark_var_as_used(p.tok.lit)
1844 }
1845 // Handle modules
1846 mut is_mod_cast := false
1847 if p.peek_tok.kind == .dot && !known_var && (language != .v || p.known_import(p.tok.lit)
1848 || p.mod.all_after_last('.') == p.tok.lit) {
1849 // p.tok.lit has been recognized as a module
1850 if language in [.c, .js, .wasm] {
1851 mod = language.str().to_upper_ascii()
1852 } else {
1853 if p.tok.lit in p.imports {
1854 // mark the imported module as used
1855 p.register_used_import(p.tok.lit)
1856 tk2 := p.peek_token(2)
1857 if p.peek_tok.kind == .dot && tk2.kind != .eof && tk2.lit.len > 0
1858 && tk2.lit[0].is_capital() {
1859 is_mod_cast = true
1860 } else if p.peek_tok.kind == .dot && tk2.kind != .eof && tk2.lit.len == 0 {
1861 // incomplete module selector must be handled by dot_expr instead
1862 ident := p.ident(language)
1863 node = ident
1864 p.add_defer_var(ident)
1865 return node
1866 }
1867 // prepend the full import
1868 mod = p.imports[p.tok.lit]
1869 } else if p.mod.all_after_last('.') == p.tok.lit {
1870 mod = p.mod
1871 }
1872 }
1873 line_nr := p.tok.line_nr
1874 p.next()
1875 p.check(.dot)
1876 if p.is_vls && p.tok.line_nr != line_nr {
1877 // The user typed `os.`, we have to display all possible `os` functions.
1878 // Turn this name expression into an Ident, since that is what expected
1879 // by `Checker.ident_autocomplete()`
1880 return ast.Ident{
1881 name: ''
1882 mod: mod
1883 pos: p.prev_tok.pos()
1884 }
1885 }
1886
1887 p.expr_mod = mod
1888 }
1889 lit0_is_capital := if p.tok.kind != .eof && p.tok.lit.len > 0 {
1890 p.tok.lit[0].is_capital()
1891 } else {
1892 false
1893 }
1894
1895 is_generic_call := p.is_generic_call()
1896 is_generic_cast := p.is_generic_cast()
1897 is_generic_struct_init := p.is_generic_struct_init()
1898 if p.peek_tok.kind == .lpar && p.tok.line_nr != p.peek_tok.line_nr
1899 && p.peek_token(2).is_next_to(p.peek_tok) {
1900 // `(` must be on same line as name token otherwise it's a ParExpr
1901 ident := p.ident(language)
1902 node = ident
1903 p.add_defer_var(ident)
1904 } else if p.peek_tok.kind == .lpar || is_generic_call || is_generic_cast
1905 || (p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr && (p.peek_token(3).kind == .lpar
1906 || p.peek_token(5).kind == .lpar)) || (p.tok.kind == .lsbr && p.peek_tok.kind == .number
1907 && p.peek_token(2).kind == .rsbr && (p.peek_token(4).kind == .lpar
1908 || p.peek_token(6).kind == .lpar)) {
1909 // ?[]foo(), ?[1]foo, foo(), foo<int>() or type() cast
1910 mut original_name := if is_array { p.peek_token(if is_fixed_array { 3 } else { 2 }).lit
1911 } else { p.tok.lit
1912 }
1913 if is_fixed_array && p.peek_token(4).kind == .dot {
1914 mod = original_name
1915 original_name = p.peek_token(5).lit
1916 } else if is_array && p.peek_token(3).kind == .dot {
1917 mod = original_name
1918 original_name = p.peek_token(4).lit
1919 }
1920 name := if mod != '' { '${mod}.${original_name}' } else { original_name }
1921 name_w_mod := p.prepend_mod(name)
1922 is_c_pointer_cast := language == .c && prev_tok_kind == .amp // `&C.abc(x)` is *always* a cast
1923 is_c_type_cast := language == .c && (original_name in ['intptr_t', 'uintptr_t']
1924 || (original_name[0].is_capital() && name in p.table.type_idxs))
1925 is_capital_after_last_dot := name.all_after_last('.')[0].is_capital()
1926 is_js_cast := language == .js && is_capital_after_last_dot
1927 // type cast. TODO: finish
1928 // if name in ast.builtin_type_names_to_idx {
1929 // handle the easy cases first, then check for an already known V typename, not shadowed by a local variable
1930 if (is_option || p.peek_tok.kind in [.lsbr, .lpar]) && (is_mod_cast
1931 || is_c_pointer_cast || is_c_type_cast || is_js_cast || is_generic_cast
1932 || (language == .v && name != '' && (is_capital_after_last_dot
1933 || name[0].is_capital() || (!known_var && (p.is_known_non_placeholder_type(name)
1934 || p.is_known_non_placeholder_type(name_w_mod)))))) {
1935 // MainLetter(x) is *always* a cast, as long as it is not `C.`
1936 // TODO: handle C.stat()
1937 start_pos := p.tok.pos()
1938 mut to_typ := p.parse_type()
1939 // this prevents inner casts to also have an `&`
1940 // example: &Foo(malloc(int(num)))
1941 // without the next line int would result in int*
1942 p.is_amp = false
1943 p.check(.lpar)
1944 mut expr := ast.empty_expr
1945 mut arg := ast.empty_expr
1946 mut has_arg := false
1947 expr = p.expr(0)
1948 // TODO, string(b, len)
1949 if p.tok.kind == .comma && to_typ.idx() == ast.string_type_idx {
1950 p.next()
1951 arg = p.expr(0) // len
1952 has_arg = true
1953 }
1954 if p.tok.kind == .comma && p.peek_tok.kind == .rpar {
1955 p.next()
1956 }
1957 end_pos := p.tok.pos()
1958 p.check(.rpar)
1959 if is_option {
1960 to_typ = to_typ.set_flag(.option)
1961 }
1962 node = ast.CastExpr{
1963 typ: to_typ
1964 typname: if to_typ != 0 { p.table.sym(to_typ).name } else { 'unknown typename' }
1965 expr: expr
1966 arg: arg
1967 has_arg: has_arg
1968 pos: start_pos.extend(end_pos)
1969 }
1970 p.expr_mod = ''
1971 return node
1972 } else {
1973 // fn_call
1974 if is_option {
1975 p.unexpected_with_pos(p.prev_tok.pos(),
1976 got: '${p.prev_tok}'
1977 )
1978 }
1979 // mod.Enum.val
1980 if p.peek_tok.kind == .dot && p.peek_token(3).kind in [.comma, .rpar] {
1981 node = p.enum_val_expr(mod)
1982 } else {
1983 node = p.call_expr(language, mod)
1984 if p.tok.kind == .lpar && p.prev_tok.line_nr == p.tok.line_nr {
1985 p.next()
1986 pos := p.tok.pos()
1987 args := p.call_args()
1988 p.check(.rpar)
1989 or_block := p.gen_or_block()
1990 node = ast.CallExpr{
1991 left: node
1992 args: args
1993 pos: pos
1994 scope: p.scope
1995 or_block: or_block
1996 is_return_used: p.expecting_value
1997 }
1998 }
1999 }
2000 }
2001 } else if !known_var && (p.peek_tok.kind == .lcbr || is_generic_struct_init)
2002 && (!p.inside_match || (p.inside_select && prev_tok_kind == .arrow && lit0_is_capital))
2003 && !p.inside_match_case && (!p.inside_if || p.inside_select)
2004 && (!p.inside_for || p.inside_select) && !p.inside_lock_exprs {
2005 alias_array_type := p.alias_array_type()
2006 if alias_array_type != ast.void_type {
2007 return p.array_init(is_option, alias_array_type)
2008 } else {
2009 // `if a == Foo{} {...}` or `match foo { Foo{} {...} }`
2010 return p.struct_init(p.mod + '.' + p.tok.lit, .normal, is_option)
2011 }
2012 } else if p.peek_tok.kind == .lcbr
2013 && ((p.inside_if && lit0_is_capital && p.tok.lit.len > 1 && !known_var && language == .v)
2014 || (p.inside_match_case && lit0_is_capital && p.tok.kind == .name
2015 && p.peek_tok.is_next_to(p.tok))) {
2016 // XTODO check iscap
2017 //|| (p.inside_match_case && p.tok.kind == .name && p.peek_tok.is_next_to(p.tok))) {
2018 // `if a == Foo{} {...}` or `match foo { Foo{} {...} }`
2019 return p.struct_init(p.mod + '.' + p.tok.lit, .normal, is_option)
2020 } else if p.peek_tok.kind == .dot && lit0_is_capital && !known_var && language == .v {
2021 // T.name selector
2022 if p.is_generic_name() && p.peek_token(3).kind != .lpar {
2023 pos := p.tok.pos()
2024 name := p.check_name()
2025 p.check(.dot)
2026 field := p.check_name()
2027 fkind := match field {
2028 'name' { ast.GenericKindField.name }
2029 'typ' { ast.GenericKindField.typ }
2030 'unaliased_typ' { ast.GenericKindField.unaliased_typ }
2031 'indirections' { ast.GenericKindField.indirections }
2032 else { ast.GenericKindField.unknown }
2033 }
2034
2035 pos.extend(p.tok.pos())
2036 return ast.SelectorExpr{
2037 expr: ast.Ident{
2038 name: name
2039 scope: p.scope
2040 }
2041 field_name: field
2042 gkind_field: fkind
2043 pos: pos
2044 scope: p.scope
2045 }
2046 }
2047 if !known_var && p.peek_token(2).kind == .name && p.peek_token(3).kind == .lpar {
2048 if lit0_is_capital && p.peek_tok.kind == .dot && language == .v {
2049 // New static method call
2050 p.expr_mod = ''
2051 return p.call_expr(language, mod)
2052 } else {
2053 p.error_with_pos('${lit0_is_capital} the receiver of the method call must be an instantiated object, e.g. `foo.bar()`',
2054 p.tok.pos())
2055 }
2056 }
2057 // `anon_fn := Foo.bar` assign static method
2058 if !known_var && lit0_is_capital && p.peek_tok.kind == .dot && language == .v
2059 && p.peek_token(2).kind == .name {
2060 type_name := p.tok.lit
2061 mut full_type_name := ''
2062 if mod != '' {
2063 full_type_name = '${mod}.${type_name}'
2064 } else if type_name in p.imported_symbols {
2065 full_type_name = p.imported_symbols[type_name]
2066 } else {
2067 full_type_name = p.prepend_mod(type_name)
2068 }
2069 static_fn_name := full_type_name + '__static__' + p.peek_token(2).lit
2070 if static_fn_name in p.table.fns {
2071 func := unsafe { p.table.fns[static_fn_name] }
2072 fn_type := ast.new_type(p.table.find_or_register_fn_type(func, false, true))
2073 pos := p.tok.pos()
2074 p.check_name()
2075 p.check(.dot)
2076 field_name := p.check_name()
2077 pos.extend(p.tok.pos())
2078 return ast.Ident{
2079 name: full_type_name + '__static__' + field_name
2080 mod: p.mod
2081 kind: .function
2082 info: ast.IdentFn{
2083 typ: fn_type
2084 }
2085 pos: pos
2086 scope: p.scope
2087 }
2088 }
2089 }
2090 return p.enum_val_expr(mod)
2091 } else if language == .js && p.peek_tok.kind == .dot && p.peek_token(2).kind == .name {
2092 // JS. function call with more than 1 dot
2093 node = p.call_expr(language, mod)
2094 } else {
2095 if p.inside_in_array && ((lit0_is_capital && !known_var && language == .v)
2096 || (p.peek_tok.kind == .dot && p.peek_token(2).lit.len > 0
2097 && p.peek_token(2).lit[0].is_capital())
2098 || p.table.find_type_idx(p.mod + '.' + p.tok.lit) > 0
2099 || p.inside_comptime_if) {
2100 type_pos := p.tok.pos()
2101 mut typ := p.parse_type()
2102 if is_option {
2103 typ = typ.set_flag(.option)
2104 }
2105 return ast.TypeNode{
2106 typ: typ
2107 pos: type_pos
2108 }
2109 } else if !known_var && language == .v && (lit0_is_capital || p.table.known_type(p.tok.lit))
2110 && p.peek_tok.kind == .pipe {
2111 start_pos := p.tok.pos()
2112 mut to_typ := p.parse_type()
2113 p.check(.lpar)
2114 expr := p.expr(0)
2115 end_pos := p.tok.pos()
2116 p.check(.rpar)
2117 node = ast.CastExpr{
2118 typ: to_typ
2119 typname: if to_typ != 0 { p.table.sym(to_typ).name } else { 'unknown type name' }
2120 expr: expr
2121 arg: ast.empty_expr
2122 has_arg: false
2123 pos: start_pos.extend(end_pos)
2124 }
2125 p.expr_mod = ''
2126 return node
2127 } else if is_option && p.tok.kind == .lsbr {
2128 return p.array_init(is_option, ast.void_type)
2129 } else if !known_var && language == .v && p.peek_tok.kind == .dot && !p.pref.is_fmt {
2130 peek_tok2 := p.peek_token(2)
2131 peek_tok3 := p.peek_token(3)
2132 mod = p.tok.lit
2133 mut n := -1
2134 for p.peek_token(n).kind == .dot && p.peek_token(n - 1).kind == .name {
2135 mod = p.peek_token(n - 1).lit + '.' + mod
2136 n -= 2
2137 }
2138 if peek_tok2.kind == .name && peek_tok2.lit.len > 0 && peek_tok2.lit[0].is_capital()
2139 && peek_tok3.kind == .lcbr
2140 && (mod.len > p.tok.lit.len || !p.known_import(p.tok.lit)) {
2141 mut msg := 'unknown module `${mod}`'
2142 if mod.len > p.tok.lit.len && p.known_import(p.tok.lit) {
2143 msg += '; did you mean `${p.tok.lit}`?'
2144 }
2145 p.error_with_pos(msg, p.tok.pos())
2146 }
2147 }
2148
2149 ident := p.ident(language)
2150 node = ident
2151 p.add_defer_var(ident)
2152 }
2153 p.expr_mod = ''
2154 return node
2155}
2156
2157enum OrBlockErrVarMode {
2158 no_err_var
2159 with_err_var
2160}
2161
2162fn (mut p Parser) or_block(err_var_mode OrBlockErrVarMode) ([]ast.Stmt, token.Pos, &ast.Scope) {
2163 was_inside_or_expr := p.inside_or_expr
2164 defer {
2165 p.inside_or_expr = was_inside_or_expr
2166 }
2167 p.inside_or_expr = true
2168
2169 mut pos := p.tok.pos()
2170 p.next()
2171 p.open_scope()
2172 or_scope := p.scope
2173 defer {
2174 p.close_scope()
2175 }
2176
2177 if err_var_mode == .with_err_var {
2178 p.scope.register(ast.Var{
2179 name: 'err'
2180 typ: ast.error_type
2181 pos: p.tok.pos()
2182 is_used: false
2183 is_stack_obj: true
2184 is_special: true
2185 })
2186 }
2187
2188 stmts := p.parse_block_no_scope(false)
2189 pos = pos.extend(p.prev_tok.pos())
2190 return stmts, pos, or_scope
2191}
2192
2193fn (mut p Parser) index_expr_part(is_gated bool) ast.Expr {
2194 part_start_pos := p.tok.pos()
2195 if p.tok.kind == .dotdot {
2196 p.next()
2197 mut high := ast.empty_expr
2198 mut has_high := false
2199 if p.tok.kind !in [.comma, .rsbr] {
2200 high = p.expr(0)
2201 has_high = true
2202 }
2203 return ast.RangeExpr{
2204 low: ast.empty_expr
2205 high: high
2206 has_high: has_high
2207 pos: part_start_pos.extend(p.prev_tok.pos())
2208 is_gated: is_gated
2209 }
2210 }
2211 expr := p.expr(0)
2212 if p.tok.kind != .dotdot {
2213 return expr
2214 }
2215 p.next()
2216 mut high := ast.empty_expr
2217 mut has_high := false
2218 if p.tok.kind !in [.comma, .rsbr] {
2219 high = p.expr(0)
2220 has_high = true
2221 }
2222 return ast.RangeExpr{
2223 low: expr
2224 high: high
2225 has_low: true
2226 has_high: has_high
2227 pos: part_start_pos.extend(p.prev_tok.pos())
2228 is_gated: is_gated
2229 }
2230}
2231
2232fn (mut p Parser) index_expr(left ast.Expr, is_gated bool) ast.IndexExpr {
2233 // left == `a` in `a[0]`
2234 start_pos := p.tok.pos()
2235 p.next() // [
2236 mut indices := []ast.Expr{}
2237 indices << p.index_expr_part(is_gated)
2238 for p.tok.kind == .comma && p.tok.pos().line_nr == start_pos.line_nr {
2239 p.next()
2240 indices << p.index_expr_part(is_gated)
2241 }
2242 pos := start_pos.extend(p.tok.pos())
2243 p.check(.rsbr)
2244 mut or_kind := ast.OrKind.absent
2245 mut or_stmts := []ast.Stmt{}
2246 mut or_pos := token.Pos{}
2247 mut or_scope := ast.empty_scope
2248 if !p.or_is_handled {
2249 // a[i] or { ... }
2250 if p.tok.kind == .key_orelse {
2251 or_stmts, or_pos, or_scope = p.or_block(.no_err_var)
2252 return ast.IndexExpr{
2253 left: left
2254 index: indices[0]
2255 indices: indices
2256 pos: pos
2257 or_expr: ast.OrExpr{
2258 kind: .block
2259 stmts: or_stmts
2260 pos: or_pos
2261 scope: or_scope
2262 }
2263 is_gated: is_gated
2264 }
2265 }
2266 // `a[i]!`
2267 if p.tok.kind == .not {
2268 or_pos = p.tok.pos()
2269 or_kind = .propagate_result
2270 or_scope = p.scope
2271 p.next()
2272 } else if p.tok.kind == .question {
2273 p.error_with_pos('`?` for propagating errors from index expressions is no longer supported, use `!` instead of `?`',
2274 p.tok.pos())
2275 }
2276 }
2277 return ast.IndexExpr{
2278 left: left
2279 index: indices[0]
2280 indices: indices
2281 pos: pos
2282 or_expr: ast.OrExpr{
2283 kind: or_kind
2284 stmts: or_stmts
2285 scope: or_scope
2286 pos: or_pos
2287 }
2288 is_gated: is_gated
2289 }
2290}
2291
2292@[inline]
2293fn (p &Parser) implicit_mutability_enabled() bool {
2294 return p.pref.disable_explicit_mutability
2295 && (!p.inside_vlib_file || p.file_path.ends_with('.vv'))
2296}
2297
2298@[inline]
2299fn (p &Parser) scope_var_is_mut(is_mut bool) bool {
2300 return is_mut || p.implicit_mutability_enabled()
2301}
2302
2303fn (mut p Parser) dot_expr(left ast.Expr) ast.Expr {
2304 prev_line := p.prev_tok.pos().line_nr
2305 p.next()
2306 if p.tok.kind == .dollar {
2307 return p.comptime_selector(left)
2308 }
2309 is_generic_call := p.is_generic_call()
2310 name_pos := p.tok.pos()
2311 // array initialization with enum shortcut [Enum.foo .bar]
2312 if !is_generic_call && p.tok.kind == .name && p.inside_array_lit && p.last_enum_name != ''
2313 && prev_line != name_pos.line_nr {
2314 p.name_error = true
2315 return ast.EnumVal{
2316 enum_name: p.last_enum_name
2317 val: p.check_name()
2318 pos: p.tok.pos()
2319 mod: p.last_enum_mod
2320 }
2321 }
2322 mut field_name := ''
2323 dot_line := p.prev_tok.pos().line_nr
2324 // check if the name is on the same line as the dot, or the dot
2325 // is on the same line as the expression before (trailing dot for chaining)
2326 if dot_line == name_pos.line_nr || prev_line == dot_line || p.tok.kind != .name {
2327 if p.is_vls && p.tok.kind != .name {
2328 if p.tok.kind in [.rpar, .rcbr] {
2329 // Simplify the dot expression for VLS, so that the parser doesn't error
2330 // `println(x.)` => `println(x)`
2331 // `x. }` => `x }` etc
2332 return left
2333 } else if name_pos.line_nr != p.tok.line_nr {
2334 return left
2335 }
2336 }
2337 field_name = p.check_name()
2338 } else {
2339 p.name_error = true
2340 }
2341 if ast.builtin_array_generic_methods_matcher.matches(field_name) && (p.tok.kind == .lpar
2342 || is_generic_call || (p.tok.kind == .not && p.peek_tok.kind == .lpar)) {
2343 if p.file_backend_mode == .v || p.file_backend_mode == .c {
2344 p.register_auto_import('builtin.closure')
2345 }
2346 p.open_scope()
2347 defer(fn) {
2348 p.close_scope()
2349 }
2350 }
2351 // ! in mutable methods
2352 if p.tok.kind == .not && p.peek_tok.kind == .lpar {
2353 p.next()
2354 }
2355 // Method call
2356 // TODO: move to fn.v call_expr()
2357 mut concrete_types := []ast.Type{}
2358 mut concrete_list_pos := p.tok.pos()
2359 if is_generic_call {
2360 // `g.foo<int>(10)`
2361 concrete_types = p.parse_concrete_types()
2362 concrete_list_pos = concrete_list_pos.extend(p.prev_tok.pos())
2363 // In case of `foo<T>()`
2364 // T is unwrapped and registered in the checker.
2365 has_generic := concrete_types.any(it.has_flag(.generic))
2366 if !has_generic {
2367 // will be added in checker
2368 p.table.register_fn_concrete_types(field_name, concrete_types)
2369 }
2370 }
2371 if p.tok.kind == .lpar {
2372 p.next()
2373 args := p.call_args()
2374 p.check(.rpar)
2375 or_block := p.gen_or_block()
2376 end_pos := p.prev_tok.pos()
2377 pos := name_pos.extend(end_pos)
2378 if field_name == 'to_fixed_size' && left is ast.ArrayInit && args.len == 0
2379 && or_block.kind == .absent {
2380 left_array := left as ast.ArrayInit
2381 if left_array.is_fixed {
2382 return left_array
2383 }
2384 if left_array.exprs.len > 0 {
2385 return ast.ArrayInit{
2386 ...left_array
2387 is_fixed: true
2388 has_val: true
2389 from_to_fixed_size: true
2390 pos: left_array.pos.extend(end_pos)
2391 }
2392 }
2393 }
2394 comments := p.eat_comments(same_line: true)
2395 mut left_node := unsafe { left }
2396 if mut left_node is ast.CallExpr {
2397 left_node.is_return_used = true
2398 }
2399 p.maybe_register_implied_vlib_import(left)
2400 mcall_expr := ast.CallExpr{
2401 left: left
2402 name: field_name
2403 kind: p.call_kind(field_name)
2404 args: args
2405 name_pos: name_pos
2406 pos: pos
2407 is_method: true
2408 concrete_types: concrete_types
2409 concrete_list_pos: concrete_list_pos
2410 raw_concrete_types: concrete_types
2411 or_block: or_block
2412 scope: p.scope
2413 comments: comments
2414 is_return_used: p.expecting_value
2415 }
2416 return mcall_expr
2417 }
2418 mut is_mut := false
2419 mut mut_pos := token.Pos{}
2420 if p.inside_match || p.inside_if_expr || p.inside_for {
2421 match left {
2422 ast.Ident, ast.SelectorExpr {
2423 is_mut = left.is_mut
2424 mut_pos = left.mut_pos
2425 }
2426 else {}
2427 }
2428 }
2429 pos := if p.name_error { left.pos().extend(name_pos) } else { name_pos }
2430
2431 mut or_kind := ast.OrKind.absent
2432 mut or_stmts := []ast.Stmt{}
2433 mut or_pos := token.Pos{}
2434 mut or_scope := ast.empty_scope
2435 if p.tok.kind == .key_orelse {
2436 or_kind = .block
2437 or_stmts, or_pos, or_scope = p.or_block(.with_err_var)
2438 } else if p.tok.kind == .not {
2439 or_kind = .propagate_result
2440 or_pos = p.tok.pos()
2441 or_scope = p.scope
2442 p.next()
2443 } else if p.tok.kind == .question {
2444 or_kind = .propagate_option
2445 or_pos = p.tok.pos()
2446 or_scope = p.scope
2447 p.next()
2448 }
2449 sel_expr := ast.SelectorExpr{
2450 expr: left
2451 field_name: field_name
2452 pos: pos
2453 is_mut: is_mut
2454 mut_pos: mut_pos
2455 or_block: ast.OrExpr{
2456 kind: or_kind
2457 stmts: or_stmts
2458 pos: or_pos
2459 scope: or_scope
2460 }
2461 scope: p.scope
2462 next_token: p.tok.kind
2463 }
2464 mut left_node := unsafe { left }
2465 if mut left_node is ast.CallExpr {
2466 left_node.is_return_used = true
2467 }
2468 p.maybe_register_implied_vlib_import(left)
2469 return sel_expr
2470}
2471
2472fn (p &Parser) vfmt_vlib_path() string {
2473 if p.pref.vlib != '' {
2474 return p.pref.vlib
2475 }
2476 return os.join_path(os.dir(pref.vexe_path()), 'vlib')
2477}
2478
2479fn (mut p Parser) maybe_register_implied_vlib_import(left ast.Expr) {
2480 if !p.pref.is_fmt || left !is ast.Ident {
2481 return
2482 }
2483 left_node := left as ast.Ident
2484 if left_node.name == '' || left_node.name in p.imports
2485 || left_node.name == p.mod.all_after_last('.')
2486 || left_node.name == p.cur_fn_name.all_after_last('.')
2487 || left_node.scope.known_var(left_node.name) {
2488 return
2489 }
2490 for _, imported_mod in p.imports {
2491 if imported_mod == left_node.name || imported_mod.all_after_last('.') == left_node.name {
2492 // The module is already imported, potentially under an alias, so this is not a missing import.
2493 return
2494 }
2495 }
2496 if left_node.name in p.imported_symbols {
2497 return
2498 }
2499 // vfmt can infer a missing import when the selector prefix matches a top-level vlib module.
2500 if os.is_dir(os.join_path(p.vfmt_vlib_path(), left_node.name)) {
2501 p.register_implied_import(left_node.name)
2502 }
2503}
2504
2505fn (mut p Parser) parse_generic_types() ([]ast.Type, []string) {
2506 mut types := []ast.Type{}
2507 mut param_names := []string{}
2508 if p.tok.kind != .lsbr {
2509 return types, param_names
2510 }
2511 end_kind := token.Kind.rsbr
2512 p.next()
2513 mut first_done := false
2514 mut count := 0
2515 for p.tok.kind !in [end_kind, .eof] {
2516 if first_done {
2517 p.check(.comma)
2518 }
2519 name := p.tok.lit
2520 if name != '' && !name[0].is_capital() {
2521 p.error('generic parameter needs to be uppercase')
2522 }
2523 if name.len > 1 {
2524 p.error('generic parameter name needs to be exactly one char')
2525 }
2526 if !util.is_generic_type_name(p.tok.lit) {
2527 p.error('`${p.tok.lit}` is a reserved name and cannot be used for generics')
2528 }
2529 if name in param_names {
2530 p.error('duplicated generic parameter `${name}`')
2531 }
2532 if count > 8 {
2533 p.error('cannot have more than 9 generic parameters')
2534 }
2535 p.check(.name)
2536 param_names << name
2537
2538 mut idx := p.table.find_type_idx(name)
2539 if idx == 0 {
2540 idx = p.table.register_sym(ast.TypeSymbol{
2541 name: name
2542 cname: util.no_dots(name)
2543 mod: p.mod
2544 kind: .any
2545 is_pub: true
2546 })
2547 }
2548 types << ast.new_type(idx).set_flag(.generic)
2549 first_done = true
2550 count++
2551 }
2552 p.check(end_kind)
2553 return types, param_names
2554}
2555
2556fn (mut p Parser) parse_concrete_types() []ast.Type {
2557 mut types := []ast.Type{}
2558 if p.tok.kind != .lsbr {
2559 return types
2560 }
2561 p.inside_fn_concrete_type = true
2562 defer {
2563 p.inside_fn_concrete_type = false
2564 }
2565 end_kind := token.Kind.rsbr
2566 p.next() // `[`
2567 mut first_done := false
2568 for p.tok.kind !in [.eof, end_kind] {
2569 if first_done {
2570 p.check(.comma)
2571 }
2572 types << p.parse_type()
2573 first_done = true
2574 }
2575 p.check(end_kind) // `]`
2576 return types
2577}
2578
2579fn (mut p Parser) string_expr() ast.Expr {
2580 is_raw := p.tok.kind == .name && p.tok.lit == 'r'
2581 is_cstr := p.tok.kind == .name && p.tok.lit == 'c'
2582 is_js_str := p.tok.kind == .name && p.tok.lit == 'js'
2583 if is_raw || is_cstr || is_js_str {
2584 p.next()
2585 }
2586 mut node := ast.empty_expr
2587 val := p.tok.lit
2588 mut pos := p.tok.pos()
2589 pos.last_line = pos.line_nr + val.count('\n')
2590 if p.peek_tok.kind != .str_dollar {
2591 p.next()
2592 node = ast.StringLiteral{
2593 val: val
2594 is_raw: is_raw
2595 language: match true {
2596 is_cstr { ast.Language.c }
2597 is_js_str { ast.Language.js }
2598 else { ast.Language.v }
2599 }
2600 pos: pos
2601 }
2602 return node
2603 }
2604 mut exprs := []ast.Expr{}
2605 mut vals := []string{}
2606 mut has_fmts := []bool{}
2607 mut fwidths := []int{}
2608 mut precisions := []int{}
2609 mut fwidth_exprs := []ast.Expr{}
2610 mut precision_exprs := []ast.Expr{}
2611 mut visible_pluss := []bool{}
2612 mut fills := []bool{}
2613 mut fmts := []u8{}
2614 mut fposs := []token.Pos{}
2615 // Handle $ interpolation
2616 p.inside_str_interp = true
2617 for p.tok.kind == .string {
2618 vals << p.tok.lit
2619 p.next()
2620 if p.tok.kind != .str_dollar {
2621 break
2622 }
2623 p.next()
2624 exprs << p.expr(0)
2625 mut has_fmt := false
2626 mut fwidth := 0
2627 mut fwidthneg := false
2628 mut fwidth_expr := ast.empty_expr
2629 // 987698 is a magic default value, unlikely to be present in user input. Note: 0 is valid precision
2630 mut precision := 987698
2631 mut precision_expr := ast.empty_expr
2632 mut visible_plus := false
2633 mut fill := false
2634 mut fmt := `_` // placeholder
2635 if p.tok.kind == .colon {
2636 p.next()
2637 // ${num:-2d}
2638 if p.tok.kind == .minus {
2639 fwidthneg = true
2640 p.next()
2641 } else if p.tok.kind == .plus {
2642 visible_plus = true
2643 p.next()
2644 }
2645 // ${num:2d}
2646 if p.tok.kind == .number {
2647 if p.peek_tok.kind == .lpar && p.tok.lit == '0' {
2648 fill = true
2649 p.next()
2650 fwidth_expr = p.string_inter_format_expr()
2651 } else {
2652 fields := p.tok.lit.split('.')
2653 if fields[0].len > 0 && fields[0][0] == `0` {
2654 fill = true
2655 }
2656 fwidth = fields[0].int()
2657 if fwidthneg {
2658 fwidth = -fwidth
2659 }
2660 if fields.len > 1 {
2661 precision = fields[1].int()
2662 }
2663 p.next()
2664 }
2665 } else if p.tok.kind == .lpar {
2666 fwidth_expr = p.string_inter_format_expr()
2667 }
2668 if fwidthneg && fwidth_expr !is ast.EmptyExpr {
2669 fwidth_expr = ast.Expr(ast.PrefixExpr{
2670 op: .minus
2671 pos: fwidth_expr.pos()
2672 right: fwidth_expr
2673 })
2674 }
2675 if p.tok.kind == .dot {
2676 p.next()
2677 if p.tok.kind == .number {
2678 precision = p.tok.lit.int()
2679 p.next()
2680 } else if p.tok.kind == .lpar {
2681 precision_expr = p.string_inter_format_expr()
2682 } else {
2683 return p.error('precision specification should be a number or `(expression)`')
2684 }
2685 }
2686 if p.tok.kind == .name {
2687 if p.tok.lit.len == 1 {
2688 fmt = p.tok.lit[0]
2689 has_fmt = true
2690 p.next()
2691 } else {
2692 return p.error('format specifier may only be one letter')
2693 }
2694 }
2695 }
2696 fwidths << fwidth
2697 fwidth_exprs << fwidth_expr
2698 has_fmts << has_fmt
2699 precisions << precision
2700 precision_exprs << precision_expr
2701 visible_pluss << visible_plus
2702 fmts << fmt
2703 fills << fill
2704 fposs << p.prev_tok.pos()
2705 }
2706 pos = pos.extend(p.prev_tok.pos())
2707 node = ast.StringInterLiteral{
2708 vals: vals
2709 exprs: exprs
2710 need_fmts: has_fmts
2711 fwidths: fwidths
2712 fwidth_exprs: fwidth_exprs
2713 precisions: precisions
2714 precision_exprs: precision_exprs
2715 pluss: visible_pluss
2716 fills: fills
2717 fmts: fmts
2718 fmt_poss: fposs
2719 pos: pos
2720 }
2721 // need_fmts: prelimery - until checker finds out if really needed
2722 p.inside_str_interp = false
2723 return node
2724}
2725
2726fn (mut p Parser) string_inter_format_expr() ast.Expr {
2727 p.check(.lpar)
2728 expr := p.expr(0)
2729 p.check(.rpar)
2730 return expr
2731}
2732
2733fn (mut p Parser) parse_number_literal() ast.Expr {
2734 mut pos := p.tok.pos()
2735 is_neg := p.tok.kind == .minus
2736 if is_neg {
2737 p.next()
2738 pos = pos.extend(p.tok.pos())
2739 }
2740 lit := p.tok.lit
2741 full_lit := if is_neg { '-' + lit } else { lit }
2742 mut node := ast.empty_expr
2743 if lit.index_any('.eE') >= 0 && lit[..2] !in ['0x', '0X', '0o', '0O', '0b', '0B'] {
2744 node = ast.FloatLiteral{
2745 val: full_lit
2746 pos: pos
2747 }
2748 } else {
2749 node = ast.IntegerLiteral{
2750 val: full_lit
2751 pos: pos
2752 }
2753 }
2754 p.next()
2755 return node
2756}
2757
2758fn (mut p Parser) const_decl() ast.ConstDecl {
2759 p.top_level_statement_start()
2760 mut attrs := []ast.Attr{}
2761 if p.attrs.len > 0 {
2762 attrs = p.attrs.clone()
2763 p.attrs = []
2764 }
2765 mut is_markused := false
2766 mut is_exported := false
2767 for ga in attrs {
2768 match ga.name {
2769 'markused' { is_markused = true }
2770 'export' { is_exported = true }
2771 else {}
2772 }
2773 }
2774 start_pos := p.tok.pos()
2775 is_pub := p.tok.kind == .key_pub
2776 if is_pub {
2777 p.next()
2778 }
2779 const_pos := p.tok.pos()
2780 if p.disallow_declarations_in_script_mode() {
2781 return ast.ConstDecl{}
2782 }
2783 p.check(.key_const)
2784 is_block := p.tok.kind == .lpar
2785 if is_block {
2786 p.next() // (
2787 }
2788 mut fields := []ast.ConstField{}
2789 mut comments := []ast.Comment{}
2790 mut end_comments := []ast.Comment{}
2791 for {
2792 comments = p.eat_comments()
2793 if is_block && p.tok.kind == .eof {
2794 p.unexpected(got: 'eof', expecting: '´)´')
2795 return ast.ConstDecl{}
2796 }
2797 if p.tok.kind == .rpar {
2798 break
2799 }
2800 pos := p.tok.pos()
2801 mut name := p.check_name()
2802 end_comments << p.eat_comments()
2803 // Handle `const C.MY_CONST u16`
2804 mut is_virtual_c_const := false
2805 mut typ := ast.void_type
2806 if name == 'C' && p.tok.kind == .dot {
2807 p.next()
2808 name += '.' + p.check_name()
2809 typ = p.parse_type()
2810 is_virtual_c_const = true
2811 }
2812 if !p.pref.translated && !p.is_translated && !is_virtual_c_const
2813 && util.contains_capital(name) {
2814 p.error_with_pos('const names cannot contain uppercase letters, use snake_case instead',
2815 pos)
2816 }
2817 full_name := if is_virtual_c_const { name } else { p.prepend_mod(name) }
2818 if p.tok.kind == .comma {
2819 p.error_with_pos('const declaration do not support multiple assign yet', p.tok.pos())
2820 if is_block {
2821 for p.tok.kind !in [.eof, .rpar] {
2822 p.next()
2823 }
2824 } else {
2825 line_nr := p.tok.line_nr
2826 for p.tok.kind != .eof && p.tok.line_nr == line_nr {
2827 p.next()
2828 }
2829 }
2830 break
2831 }
2832 // Allow for `const x := 123`, and for `const x = 123` too.
2833 // Supporting `const x := 123` in addition to `const x = 123`, makes extracting local variables to constants
2834 // much less annoying, while prototyping:
2835 if p.tok.kind == .decl_assign {
2836 p.check(.decl_assign)
2837 } else {
2838 if !is_virtual_c_const {
2839 p.check(.assign)
2840 }
2841 }
2842 end_comments << p.eat_comments()
2843 if p.tok.kind == .key_fn && !is_virtual_c_const {
2844 p.error('const initializer fn literal is not a constant')
2845 return ast.ConstDecl{}
2846 }
2847 if p.tok.kind == .eof {
2848 p.unexpected(got: 'eof', expecting: 'an expression')
2849 return ast.ConstDecl{}
2850 }
2851 mut expr := ast.Expr{}
2852 if !is_virtual_c_const {
2853 old_inside_assign_rhs := p.inside_assign_rhs
2854 p.inside_assign_rhs = true
2855 expr = p.expr(0)
2856 p.inside_assign_rhs = old_inside_assign_rhs
2857 }
2858 // we need `end_comments` when in `vls` mode too
2859 if is_block || p.pref.is_vls {
2860 end_comments << p.eat_comments(same_line: true)
2861 }
2862 mut field := ast.ConstField{
2863 name: full_name
2864 mod: p.mod
2865 is_pub: is_pub
2866 expr: expr
2867 pos: pos.extend(expr.pos())
2868 attrs: attrs
2869 comments: comments
2870 end_comments: end_comments
2871 is_markused: is_markused
2872 is_exported: is_exported
2873 is_virtual_c: is_virtual_c_const
2874 }
2875 if is_virtual_c_const {
2876 field.typ = typ
2877 }
2878 fields << field
2879 p.table.global_scope.register(field)
2880 if p.pref.is_vls {
2881 key := 'const_${full_name}'
2882 // Fixme: because ConstDecl has no name, we can't access ConstDecl via name
2883 // So the comment before the `const` keyword will be set to the first field's comment
2884 // But as `vfmt` suggest every const should has a single line, no const block, this should be no problem
2885 doc := if fields.len == 1 {
2886 p.cur_comments << comments
2887 p.keyword_comments_to_string(name, p.cur_comments) +
2888 p.comments_to_string(end_comments)
2889 } else {
2890 p.comments_to_string(comments) + p.comments_to_string(end_comments)
2891 }
2892
2893 val := ast.VlsInfo{
2894 pos: field.pos
2895 doc: doc
2896 }
2897 p.table.register_vls_info(key, val)
2898 }
2899 comments = []
2900 if is_block {
2901 end_comments = []
2902 }
2903 if !is_block {
2904 break
2905 }
2906 }
2907 p.top_level_statement_end()
2908 if is_block {
2909 p.check(.rpar)
2910 } else {
2911 comments << p.eat_comments(same_line: true)
2912 }
2913 const_decl := ast.ConstDecl{
2914 pos: start_pos.extend_with_last_line(const_pos, p.prev_tok.line_nr)
2915 fields: fields
2916 is_pub: is_pub
2917 end_comments: comments
2918 is_block: is_block
2919 attrs: attrs
2920 }
2921 return const_decl
2922}
2923
2924fn (mut p Parser) return_stmt() ast.Return {
2925 first_pos := p.tok.pos()
2926 p.next()
2927 // no return
2928 mut comments := p.eat_comments()
2929 if p.tok.kind == .rcbr || (p.tok.kind == .name && p.peek_tok.kind == .colon) {
2930 return ast.Return{
2931 scope: p.scope
2932 comments: comments
2933 pos: first_pos
2934 }
2935 }
2936 // return exprs
2937 old_assign_rhs := p.inside_assign_rhs
2938 p.inside_assign_rhs = true
2939 exprs := p.expr_list(true)
2940 p.inside_assign_rhs = old_assign_rhs
2941 end_pos := exprs.last().pos()
2942 return ast.Return{
2943 scope: p.scope
2944 exprs: exprs
2945 comments: comments
2946 pos: first_pos.extend(end_pos)
2947 }
2948}
2949
2950// left hand side of `=` or `:=` in `a,b,c := 1,2,3`
2951fn (mut p Parser) global_decl() ast.GlobalDecl {
2952 mut attrs := []ast.Attr{}
2953 if p.attrs.len > 0 {
2954 attrs = p.attrs.clone()
2955 p.attrs = []
2956 }
2957
2958 mut is_markused := false
2959 mut is_exported := false
2960 mut is_weak := false
2961 mut is_hidden := false
2962 mut is_extern := false
2963 for ga in attrs {
2964 match ga.name {
2965 'export' { is_exported = true }
2966 'markused' { is_markused = true }
2967 'weak' { is_weak = true }
2968 'hidden' { is_hidden = true }
2969 'c_extern' { is_extern = true }
2970 else {}
2971 }
2972 }
2973
2974 // Parser always parses __global declarations correctly
2975 // Checker will report an error if globals are not enabled
2976 start_pos := p.tok.pos()
2977 p.check(.key_global)
2978 if p.disallow_declarations_in_script_mode() {
2979 return ast.GlobalDecl{}
2980 }
2981 is_block := p.tok.kind == .lpar
2982 if is_block {
2983 p.next() // (
2984 }
2985 mut fields := []ast.GlobalField{}
2986 mut comments := []ast.Comment{}
2987 for {
2988 comments = p.eat_comments()
2989 mut is_volatile := false
2990 mut is_const := false
2991 for p.tok.kind in [.key_const, .key_volatile] {
2992 match p.tok.kind {
2993 .key_const {
2994 is_const = true
2995 }
2996 .key_volatile {
2997 is_volatile = true
2998 }
2999 else {}
3000 }
3001
3002 p.next()
3003 }
3004 if is_block && p.tok.kind == .eof {
3005 p.unexpected(got: 'eof', expecting: '`)`')
3006 return ast.GlobalDecl{}
3007 }
3008 if p.tok.kind == .rpar {
3009 break
3010 }
3011 language := p.parse_language()
3012
3013 pos := p.tok.pos()
3014 mut name := p.check_name()
3015 has_expr := p.tok.kind == .assign
3016 mut expr := ast.empty_expr
3017 mut typ := ast.void_type
3018 mut typ_pos := token.Pos{}
3019 if has_expr {
3020 p.next() // =
3021 old_assign_rhs := p.inside_assign_rhs
3022 p.inside_assign_rhs = true
3023 expr = p.expr(0)
3024 p.inside_assign_rhs = old_assign_rhs
3025 match mut expr {
3026 ast.CastExpr, ast.StructInit, ast.ArrayInit, ast.ChanInit {
3027 typ = expr.typ
3028 }
3029 ast.BoolLiteral, ast.IsRefType {
3030 typ = ast.bool_type
3031 }
3032 ast.CharLiteral {
3033 typ = ast.char_type
3034 }
3035 ast.FloatLiteral {
3036 typ = ast.f64_type
3037 }
3038 ast.IntegerLiteral, ast.SizeOf {
3039 typ = ast.int_type
3040 }
3041 ast.StringLiteral, ast.StringInterLiteral {
3042 typ = ast.string_type
3043 }
3044 else {
3045 // type will be deduced by checker
3046 }
3047 }
3048 } else {
3049 typ_pos = p.tok.pos()
3050 typ = p.parse_type()
3051 }
3052 if language == .c {
3053 name = 'C.' + name
3054 }
3055 field := ast.GlobalField{
3056 name: name
3057 has_expr: has_expr
3058 expr: expr
3059 pos: pos
3060 typ_pos: typ_pos
3061 typ: typ
3062 comments: comments
3063 is_markused: is_markused
3064 is_volatile: is_volatile
3065 is_const: is_const
3066 is_exported: is_exported
3067 is_weak: is_weak
3068 is_hidden: is_hidden
3069 is_extern: is_extern
3070 language: language
3071 }
3072 fields << field
3073 if name !in ast.global_reserved_type_names {
3074 p.table.global_scope.register(field)
3075 }
3076 comments = []
3077 if !is_block {
3078 break
3079 }
3080 }
3081 if is_block {
3082 p.check(.rpar)
3083 }
3084 global_decl := ast.GlobalDecl{
3085 pos: start_pos.extend(p.prev_tok.pos())
3086 mod: p.mod
3087 fields: fields
3088 end_comments: comments
3089 is_block: is_block
3090 attrs: attrs
3091 }
3092 if p.pref.is_vls {
3093 for i, f in fields {
3094 mut key := 'global_${f.name}'
3095 // Fixme: because GlobalDecl has no name, we can't access GlobalDecl via name
3096 // So the comment before the `__global` keyword will be set to the first field's comment
3097 doc := if i == 0 {
3098 p.cur_comments << f.comments
3099 p.keyword_comments_to_string(f.name, p.cur_comments)
3100 } else {
3101 p.comments_to_string(f.comments)
3102 }
3103 val := ast.VlsInfo{
3104 pos: f.pos
3105 doc: doc
3106 }
3107 p.table.register_vls_info(key, val)
3108
3109 // register another `module specific global`
3110 key = 'global_${p.prepend_mod(f.name)}'
3111 p.table.register_vls_info(key, val)
3112 }
3113 }
3114 return global_decl
3115}
3116
3117fn source_name(name string) string {
3118 if token.is_key(name) {
3119 return '@${name}'
3120 }
3121 return name
3122}
3123
3124fn (mut p Parser) type_decl() ast.TypeDecl {
3125 attrs := p.attrs
3126 start_pos := p.tok.pos()
3127 is_pub := p.tok.kind == .key_pub
3128 if is_pub {
3129 p.next()
3130 }
3131 p.check(.key_type)
3132 mut comments_before_key_type := if p.pref.is_vls { p.cur_comments.clone() } else { [] }
3133 end_pos := p.tok.pos()
3134 decl_pos := start_pos.extend(end_pos)
3135 name_pos := p.tok.pos()
3136 if p.disallow_declarations_in_script_mode() {
3137 return ast.SumTypeDecl{}
3138 }
3139 if p.is_vls && p.tok.is_key() {
3140 // End parsing after `type ` in vls mode to avoid lots of junk errors
3141 // If next token is a key, the type wasn't finished
3142 p.error('expecting type name')
3143 p.should_abort = true
3144 return ast.AliasTypeDecl{}
3145 }
3146 mut name := p.check_name()
3147 mut language := ast.Language.v
3148 if name.len == 1 && name[0].is_capital() {
3149 if name == 'C' && p.tok.kind == .dot {
3150 p.next() // .
3151 name = 'C.' + p.check_name()
3152 language = .c
3153 } else {
3154 p.error_with_pos('single letter capital names are reserved for generic template types',
3155 name_pos)
3156 return ast.FnTypeDecl{}
3157 }
3158 }
3159 if p.is_imported_symbol(name) {
3160 p.error_with_pos('cannot register alias `${name}`, this type was already imported', end_pos)
3161 return ast.AliasTypeDecl{}
3162 }
3163 mut sum_variants := []ast.TypeNode{}
3164 generic_types, _ := p.parse_generic_types()
3165 decl_pos_with_generics := decl_pos.extend(p.prev_tok.pos())
3166 p.check(.assign)
3167 mut type_pos := p.tok.pos()
3168 mut comments := []ast.Comment{}
3169 if p.tok.kind == .key_fn && p.is_fn_type_decl() {
3170 // function type: `type mycallback = fn(string, int)`
3171 fn_name := p.prepend_mod(name)
3172 fn_type := p.parse_fn_type(fn_name, generic_types)
3173 p.table.sym(fn_type).is_pub = is_pub
3174 type_pos = type_pos.extend(p.tok.pos())
3175 comments = p.eat_comments(same_line: true)
3176 p.attrs = []
3177 fn_type_decl := ast.FnTypeDecl{
3178 name: fn_name
3179 mod: p.mod
3180 is_pub: is_pub
3181 typ: fn_type
3182 pos: decl_pos
3183 type_pos: type_pos
3184 comments: comments
3185 generic_types: generic_types
3186 attrs: attrs
3187 is_markused: attrs.contains('markused')
3188 }
3189 if p.pref.is_vls {
3190 key := 'fntype_${fn_name}'
3191 val := ast.VlsInfo{
3192 pos: decl_pos
3193 doc: p.keyword_comments_to_string(name, comments_before_key_type) +
3194 p.comments_to_string(comments)
3195 }
3196 p.table.register_vls_info(key, val)
3197 }
3198 return fn_type_decl
3199 }
3200 sum_variants << p.parse_sum_type_variants()
3201 // type SumType = Aaa | Bbb | Ccc
3202 if sum_variants.len > 1 {
3203 for variant in sum_variants {
3204 if variant.typ == 0 {
3205 // the type symbol is probably coming from another .v file
3206 continue
3207 }
3208 variant_sym := p.table.sym(variant.typ)
3209 // TODO: implement this check for error too
3210 if variant_sym.kind == .none {
3211 p.error_with_pos('named sum type cannot have none as its variant', variant.pos)
3212 return ast.AliasTypeDecl{}
3213 }
3214 }
3215 variant_types := sum_variants.map(it.typ)
3216 prepend_mod_name := p.prepend_mod(name)
3217 typ := p.table.register_sym(ast.TypeSymbol{
3218 kind: .sum_type
3219 name: prepend_mod_name
3220 cname: util.no_dots(prepend_mod_name)
3221 mod: p.mod
3222 info: ast.SumType{
3223 variants: variant_types
3224 is_generic: generic_types.len > 0
3225 generic_types: generic_types
3226 name_pos: name_pos
3227 }
3228 is_pub: is_pub
3229 })
3230 if typ in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx]
3231 && !p.pref.is_fmt {
3232 p.error_with_pos('cannot register sum type `${name}`, another type with this name exists',
3233 name_pos)
3234 return ast.SumTypeDecl{}
3235 }
3236 node := ast.SumTypeDecl{
3237 name: name
3238 mod: p.mod
3239 typ: typ
3240 is_pub: is_pub
3241 variants: sum_variants
3242 generic_types: generic_types
3243 attrs: p.attrs
3244 pos: decl_pos
3245 name_pos: name_pos
3246 is_markused: attrs.contains('markused')
3247 }
3248 p.table.register_sumtype(node)
3249 if p.pref.is_vls {
3250 key := 'sumtype_${p.prepend_mod(name)}'
3251 val := ast.VlsInfo{
3252 pos: node.pos
3253 doc: p.keyword_comments_to_string(name, comments_before_key_type) +
3254 p.comments_to_string(sum_variants[sum_variants.len - 1].end_comments)
3255 }
3256 p.table.register_vls_info(key, val)
3257 }
3258 return node
3259 }
3260 // type MyType = int
3261 if generic_types.len > 0 {
3262 p.error_with_pos('generic type aliases are not yet implemented', decl_pos_with_generics)
3263 return ast.AliasTypeDecl{}
3264 }
3265 // sum_variants will have only one element
3266 parent_type := sum_variants[0].typ
3267 pidx := parent_type.idx()
3268 mut parent_language := ast.Language.v
3269 if parent_type != 0 {
3270 parent_sym := p.table.sym(parent_type)
3271 parent_language = parent_sym.language
3272 p.check_for_impure_v(parent_sym.language, decl_pos)
3273 }
3274 prepend_mod_name := if language == .v { p.prepend_mod(name) } else { name } // `C.time_t`, not `time.C.time_t`
3275 idx := p.table.register_sym(ast.TypeSymbol{
3276 kind: .alias
3277 name: prepend_mod_name
3278 cname: util.no_dots(prepend_mod_name)
3279 mod: p.mod
3280 parent_idx: pidx
3281 info: ast.Alias{
3282 parent_type: parent_type
3283 language: parent_language
3284 name_pos: name_pos
3285 }
3286 is_pub: is_pub
3287 })
3288 type_end_pos := p.prev_tok.pos()
3289 if idx in [ast.string_type_idx, ast.rune_type_idx, ast.array_type_idx, ast.map_type_idx]
3290 && !p.pref.is_fmt {
3291 p.error_with_pos('cannot register alias `${name}`, another type with this name exists',
3292 name_pos)
3293 return ast.AliasTypeDecl{}
3294 }
3295 if idx == pidx {
3296 type_alias_pos := sum_variants[0].pos
3297 p.error_with_pos('a type alias can not refer to itself: ${name}',
3298 decl_pos.extend(type_alias_pos))
3299 return ast.AliasTypeDecl{}
3300 }
3301 comments = sum_variants[0].end_comments.clone()
3302 p.attrs = []
3303 alias_type_decl := ast.AliasTypeDecl{
3304 name: name
3305 mod: p.mod
3306 is_pub: is_pub
3307 typ: idx
3308 parent_type: parent_type
3309 type_pos: type_pos.extend(type_end_pos)
3310 pos: decl_pos
3311 comments: comments
3312 is_markused: attrs.contains('markused')
3313 attrs: attrs
3314 }
3315 if p.pref.is_vls {
3316 key := 'aliastype_${p.prepend_mod(name)}'
3317 val := ast.VlsInfo{
3318 pos: alias_type_decl.pos
3319 doc: p.keyword_comments_to_string(name, comments_before_key_type) +
3320 p.comments_to_string(comments)
3321 }
3322 p.table.register_vls_info(key, val)
3323 }
3324 return alias_type_decl
3325}
3326
3327fn (mut p Parser) assoc() ast.Assoc {
3328 var_name := p.check_name()
3329 pos := p.tok.pos()
3330 mut v := p.scope.find_var(var_name) or {
3331 p.error('unknown variable `${var_name}`')
3332 return ast.Assoc{
3333 scope: unsafe { nil }
3334 }
3335 }
3336 v.is_used = true
3337 mut fields := []string{}
3338 mut vals := []ast.Expr{}
3339 p.check(.pipe)
3340 for p.tok.kind != .eof {
3341 fields << p.check_name()
3342 p.check(.colon)
3343 expr := p.expr(0)
3344 vals << expr
3345 if p.tok.kind == .comma {
3346 p.next()
3347 }
3348 if p.tok.kind == .rcbr {
3349 break
3350 }
3351 }
3352 return ast.Assoc{
3353 var_name: var_name
3354 fields: fields
3355 exprs: vals
3356 pos: pos
3357 scope: p.scope
3358 }
3359}
3360
3361fn (p &Parser) new_true_expr() ast.Expr {
3362 return ast.BoolLiteral{
3363 val: true
3364 pos: p.tok.pos()
3365 }
3366}
3367
3368fn (mut p Parser) top_level_statement_start() {
3369 if p.scanner.comments_mode == .toplevel_comments {
3370 p.scanner.set_is_inside_toplevel_statement(true)
3371 p.rewind_scanner_to_current_token_in_new_mode()
3372 $if trace_scanner ? {
3373 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} ...')
3374 }
3375 }
3376}
3377
3378fn (mut p Parser) top_level_statement_end() {
3379 if p.scanner.comments_mode == .toplevel_comments {
3380 p.scanner.set_is_inside_toplevel_statement(false)
3381 p.rewind_scanner_to_current_token_in_new_mode()
3382 $if trace_scanner ? {
3383 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} ...')
3384 }
3385 }
3386}
3387
3388fn (mut p Parser) rewind_scanner_to_current_token_in_new_mode() {
3389 // Go back and rescan some tokens, ensuring that the parser's
3390 // lookahead buffer p.peek_tok .. p.peek_token(3), will now contain
3391 // the correct tokens (possible comments), for the new mode
3392 // This refilling of the lookahead buffer is needed for the
3393 // .toplevel_comments parsing mode.
3394 tidx := p.tok.tidx
3395 p.scanner.set_current_tidx(tidx - 5)
3396 no_token := token.Token{}
3397 p.prev_tok = no_token
3398 p.tok = no_token
3399 p.peek_tok = no_token // requires 2 calls p.next() or check p.tok.kind != token.Kind.unknown
3400 p.next()
3401 for {
3402 p.next()
3403 // eprintln('rewinding to ${p.tok.tidx:5} | goal: ${tidx:5}')
3404 if tidx == p.tok.tidx {
3405 break
3406 }
3407 }
3408}
3409
3410fn (mut p Parser) unsafe_stmt() ast.Stmt {
3411 mut pos := p.tok.pos()
3412 p.next()
3413 if p.tok.kind != .lcbr {
3414 return p.error_with_pos('please use `unsafe {`', p.tok.pos())
3415 }
3416 p.next()
3417 if p.inside_unsafe && !p.inside_defer {
3418 err := p.error_with_pos('already inside `unsafe` block', pos)
3419 p.recover_until_closing_rcbr()
3420 return err
3421 }
3422 p.inside_unsafe = true
3423 p.open_scope() // needed in case of `unsafe {stmt}`
3424 sc := p.scope
3425 defer {
3426 p.inside_unsafe = false
3427 p.close_scope()
3428 }
3429 if p.tok.kind == .rcbr {
3430 // `unsafe {}`
3431 pos.update_last_line(p.tok.line_nr)
3432 p.next()
3433 return ast.Block{
3434 scope: sc
3435 is_unsafe: true
3436 pos: pos
3437 }
3438 }
3439 stmt := p.stmt(false)
3440 if p.tok.kind == .rcbr {
3441 if stmt is ast.ExprStmt {
3442 // `unsafe {expr}`
3443 if stmt.expr.is_expr() {
3444 p.next()
3445 pos.update_last_line(p.prev_tok.line_nr)
3446 ue := ast.UnsafeExpr{
3447 expr: stmt.expr
3448 pos: pos
3449 }
3450 // parse e.g. `unsafe {expr}.foo()`
3451 expr := p.expr_with_left(ue, 0, p.is_stmt_ident)
3452 return ast.ExprStmt{
3453 expr: expr
3454 pos: pos
3455 }
3456 }
3457 }
3458 }
3459 // unsafe {stmts}
3460 mut stmts := [stmt]
3461 for p.tok.kind != .rcbr {
3462 stmts << p.stmt(false)
3463 }
3464 p.next()
3465 pos.update_last_line(p.tok.line_nr)
3466 return ast.Block{
3467 stmts: stmts
3468 scope: sc
3469 is_unsafe: true
3470 pos: pos
3471 }
3472}
3473
3474fn (mut p Parser) disallow_declarations_in_script_mode() bool {
3475 if p.script_mode {
3476 p.note_with_pos('script mode started here', p.script_mode_start_token.pos())
3477 p.error_with_pos('all definitions must occur before code in script mode', p.tok.pos())
3478 return true
3479 }
3480 return false
3481}
3482
3483fn (mut p Parser) trace[T](fbase string, x &T) {
3484 if p.file_base == fbase {
3485 println('> p.trace | ${fbase:-10s} | ${voidptr(x):16} | ${x}')
3486 }
3487}
3488
3489@[params]
3490struct ParserShowParams {
3491pub:
3492 msg string
3493 reach int = 3
3494}
3495
3496fn (mut p Parser) show(params ParserShowParams) {
3497 mut context := []string{}
3498 for i in -params.reach .. params.reach + 1 {
3499 x := p.peek_token(i).str()
3500 if i == 0 {
3501 context << ' ${x:-30s} '
3502 continue
3503 }
3504 context << x
3505 }
3506 location := '${p.file_display_path}:${p.tok.line_nr}:'
3507 println('>> ${location:-40s} ${params.msg} ${context.join(' ')}')
3508}
3509
3510fn (mut p Parser) add_defer_var(ident ast.Ident) {
3511 if p.inside_defer && p.defer_mode == .function {
3512 if !p.defer_vars.any(it.name == ident.name && it.mod == ident.mod)
3513 && ident.name !in ['err', 'it'] {
3514 p.defer_vars << ident
3515 }
3516 }
3517}
3518
3519// skip `{...}`
3520fn (mut p Parser) skip_scope() {
3521 mut br_cnt := 0
3522 for {
3523 match p.tok.kind {
3524 .lcbr { br_cnt++ }
3525 .rcbr { br_cnt-- }
3526 .eof { break }
3527 else {}
3528 }
3529
3530 if br_cnt == 0 {
3531 break
3532 }
3533 p.next()
3534 }
3535 if p.tok.kind == .rcbr {
3536 p.next()
3537 }
3538}
3539
3540// keyword_comments_to_string will search line by line in `comments` for which line starts with `keyword`,
3541// and then construct following comments into a string.
3542// If no `keyword` found in each line of comments' beginning, then return ''
3543// e.g
3544// keyword = 'MyS'
3545//
3546// this is first comment
3547// this is second comment
3548// this is third comment
3549// MyS is a struct ...
3550// Note:...
3551//
3552// will return a string:
3553// 'MyS is a struct ...\nNote:...'
3554
3555fn (mut p Parser) keyword_comments_to_string(keyword string, comments []ast.Comment) string {
3556 mut sb := strings.new_builder(128)
3557 mut is_found_keyword := false
3558 for line in comments {
3559 trim_line := if line.text.len > 0 && line.text[0] == 1 {
3560 // skip ´\x01´
3561 line.text[1..].trim_space()
3562 } else {
3563 line.text.trim_space()
3564 }
3565 if !is_found_keyword && trim_line.starts_with(keyword) {
3566 is_found_keyword = true
3567 }
3568 if is_found_keyword {
3569 sb.writeln(trim_line)
3570 }
3571 }
3572 return sb.str()
3573}
3574
3575// comments_to_string will construct all in `comments` into a string.
3576fn (mut p Parser) comments_to_string(comments []ast.Comment) string {
3577 mut sb := strings.new_builder(128)
3578 for line in comments {
3579 trim_line := if line.text.len > 0 && line.text[0] == 1 {
3580 // skip ´\x01´
3581 line.text[1..].trim_space()
3582 } else {
3583 line.text.trim_space()
3584 }
3585 sb.writeln(trim_line)
3586 }
3587 return sb.str()
3588}
3589