v2 / vlib / v / parser / comptime.v
934 lines · 906 sloc · 24.25 KB · 8b5f74e6a7b52c1100cb52ef2832fd0c090fb407
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 os
7import v.ast
8import v.token
9import v.errors
10import v.util
11import v.vmod
12
13const supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig', 'compile_error',
14 'compile_warn', 'd', 'res', 'zero', 'new']
15const supported_comptime_for_kinds = ['methods', 'fields', 'values', 'variants', 'attributes',
16 'params']
17const comptime_types = ['map', 'array', 'array_dynamic', 'array_fixed', 'int', 'float', 'struct',
18 'interface', 'enum', 'sumtype', 'alias', 'function', 'option', 'shared', 'string', 'pointer',
19 'voidptr']
20
21fn find_veb_template_relative_to_vmod(compiled_vfile_path string, relative_path string) ?string {
22 mut mcache := vmod.get_cache()
23 vmod_file_location := mcache.get_by_file(compiled_vfile_path)
24 if vmod_file_location.vmod_file == '' {
25 return none
26 }
27 vmod_template_path := os.join_path(vmod_file_location.vmod_folder, relative_path)
28 if !os.exists(vmod_template_path) {
29 return none
30 }
31 return os.real_path(vmod_template_path)
32}
33
34fn find_existing_veb_template_in_vmod(compiled_vfile_path string, relative_paths []string) ?string {
35 for relative_path in relative_paths {
36 if path := find_veb_template_relative_to_vmod(compiled_vfile_path, relative_path) {
37 return path
38 }
39 }
40 return none
41}
42
43@[inline]
44fn is_supported_comptime_for_kind(name string) bool {
45 return name in supported_comptime_for_kinds
46}
47
48fn (mut p Parser) parse_comptime_type() ast.ComptimeType {
49 pos := p.tok.pos()
50 p.check(.dollar)
51 name := p.check_name()
52 if name !in comptime_types {
53 p.error('unsupported compile-time type `${name}`: only ${comptime_types} are supported')
54 }
55 mut kind := ast.ComptimeTypeKind.unknown
56 kind = match name {
57 'map' {
58 .map
59 }
60 'struct' {
61 .struct
62 }
63 'interface' {
64 .iface
65 }
66 'int' {
67 .int
68 }
69 'float' {
70 .float
71 }
72 'alias' {
73 .alias
74 }
75 'function' {
76 .function
77 }
78 'array' {
79 .array
80 }
81 'array_fixed' {
82 .array_fixed
83 }
84 'array_dynamic' {
85 .array_dynamic
86 }
87 'enum' {
88 .enum
89 }
90 'sumtype' {
91 .sum_type
92 }
93 'option' {
94 .option
95 }
96 'shared' {
97 .shared
98 }
99 'string' {
100 .string
101 }
102 'pointer' {
103 .pointer
104 }
105 'voidptr' {
106 .voidptr
107 }
108 else {
109 .unknown
110 }
111 }
112
113 return ast.ComptimeType{
114 kind: kind
115 pos: pos
116 }
117}
118
119// // #include, #flag, #v
120fn (mut p Parser) hash() ast.HashStmt {
121 pos := p.tok.pos()
122 val := p.tok.lit
123 kind := val.all_before(' ')
124 attrs := p.attrs
125 p.next()
126 mut main_str := ''
127 mut msg := ''
128 mut ct_low_level_cond := ''
129 content := val.all_after('${kind} ').all_before('//')
130 if content.contains(' #') {
131 main_str = content.all_before(' #').trim_space()
132 msg = content.all_after(' #').trim_space()
133 } else {
134 main_str = content.trim_space()
135 msg = ''
136 }
137
138 // Detect OS-specific conditions like `#include linux <pty.h>` or `#include darwin "util.h"`.
139 // The condition is the first word before the actual include path.
140 if kind in ['include', 'preinclude', 'postinclude', 'insert'] {
141 first_space := main_str.index_u8(` `)
142 if first_space > 0 {
143 maybe_cond := main_str[..first_space]
144 if maybe_cond in ast.valid_comptime_not_user_defined {
145 ct_low_level_cond = maybe_cond
146 main_str = main_str[first_space + 1..].trim_space()
147 }
148 }
149 }
150
151 mut is_use_once := false
152 for fna in attrs {
153 match fna.name {
154 'use_once' { is_use_once = true }
155 else {}
156 }
157 }
158
159 return ast.HashStmt{
160 mod: p.mod
161 source_file: p.file_path
162 val: val
163 kind: kind
164 main: main_str
165 msg: msg
166 pos: pos
167 attrs: attrs
168 is_use_once: is_use_once
169 ct_low_level_cond: ct_low_level_cond
170 }
171}
172
173const error_msg = 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()`, `\$veb.html()`, `\$compile_error()`, `\$compile_warn()`, `\$d()`, `\$res()`, `\$zero()` and `\$new()` comptime functions are supported right now'
174
175fn (p &Parser) resolve_tmpl_path_expr(expr ast.Expr) ?string {
176 return p.resolve_tmpl_path_expr_with_depth(expr, 0)
177}
178
179fn (p &Parser) resolve_tmpl_path_expr_with_depth(expr ast.Expr, depth int) ?string {
180 if depth > 50 {
181 return none
182 }
183 match expr {
184 ast.AtExpr {
185 return expr.name
186 }
187 ast.CallExpr {
188 match expr.name {
189 'os.join_path' {
190 if expr.args.len == 0 {
191 return ''
192 }
193 mut path := p.resolve_tmpl_path_expr_with_depth(expr.args[0].expr, depth + 1)?
194 for arg in expr.args[1..] {
195 path = os.join_path_single(path, p.resolve_tmpl_path_expr_with_depth(arg.expr,
196
197 depth + 1)?)
198 }
199 return path
200 }
201 'os.join_path_single' {
202 if expr.args.len != 2 {
203 return none
204 }
205 return os.join_path_single(p.resolve_tmpl_path_expr_with_depth(expr.args[0].expr,
206
207 depth + 1)?, p.resolve_tmpl_path_expr_with_depth(expr.args[1].expr, depth +
208 1)?)
209 }
210 else {
211 return none
212 }
213 }
214 }
215 ast.Ident {
216 if var := p.scope.find_var(expr.name) {
217 return p.resolve_tmpl_path_expr_with_depth(var.expr, depth + 1)
218 }
219 if expr.name == 'os.path_separator' {
220 return os.path_separator
221 }
222 if imported_name := p.imported_symbols[expr.name] {
223 if const_field := p.table.global_scope.find_const(imported_name) {
224 return p.resolve_tmpl_path_expr_with_depth(const_field.expr, depth + 1)
225 }
226 }
227 if const_field := p.table.global_scope.find_const(expr.name) {
228 return p.resolve_tmpl_path_expr_with_depth(const_field.expr, depth + 1)
229 }
230 if const_field := p.table.global_scope.find_const('${expr.mod}.${expr.name}') {
231 return p.resolve_tmpl_path_expr_with_depth(const_field.expr, depth + 1)
232 }
233 if const_field := p.table.global_scope.find_const('${p.mod}.${expr.name}') {
234 return p.resolve_tmpl_path_expr_with_depth(const_field.expr, depth + 1)
235 }
236 return none
237 }
238 ast.InfixExpr {
239 if expr.op != .plus {
240 return none
241 }
242 return p.resolve_tmpl_path_expr_with_depth(expr.left, depth + 1)? +
243 p.resolve_tmpl_path_expr_with_depth(expr.right, depth + 1)?
244 }
245 ast.ParExpr {
246 return p.resolve_tmpl_path_expr_with_depth(expr.expr, depth + 1)
247 }
248 ast.SelectorExpr {
249 module_name := p.resolve_tmpl_path_module_name(expr.expr) or { return none }
250 if module_name == 'os' && expr.field_name == 'path_separator' {
251 return os.path_separator
252 }
253 if const_field := p.table.global_scope.find_const('${module_name}.${expr.field_name}') {
254 return p.resolve_tmpl_path_expr_with_depth(const_field.expr, depth + 1)
255 }
256 return none
257 }
258 ast.StringLiteral {
259 return expr.val
260 }
261 else {
262 return none
263 }
264 }
265}
266
267fn (p &Parser) resolve_tmpl_path_module_name(expr ast.Expr) ?string {
268 match expr {
269 ast.Ident {
270 if module_name := p.imports[expr.name] {
271 return module_name
272 }
273 return expr.name
274 }
275 ast.SelectorExpr {
276 return '${p.resolve_tmpl_path_module_name(expr.expr)?}.${expr.field_name}'
277 }
278 else {
279 return none
280 }
281 }
282}
283
284fn (mut p Parser) resolve_tmpl_pseudo_variables(path string, pos token.Pos) ?string {
285 mut resolved := path
286 source_path := os.real_path(p.scanner.file_path)
287 if resolved.contains('@VEXEROOT') {
288 resolved = resolved.replace('@VEXEROOT', p.pref.vroot)
289 }
290 if resolved.contains('@VMODROOT') {
291 resolved = util.resolve_vmodroot(resolved, source_path) or {
292 p.error_with_pos(err.msg(), pos)
293 return none
294 }
295 }
296 if resolved.contains('@DIR') {
297 resolved = resolved.replace('@DIR', os.dir(source_path))
298 }
299 return resolved
300}
301
302fn (p &Parser) is_comptime_type_selector_at(offset int) bool {
303 if p.peek_token(offset).kind == .key_typeof {
304 return true
305 }
306 if p.peek_token(offset).kind != .name {
307 return false
308 }
309 mut n := offset + 1
310 for p.peek_token(n).kind == .dot && p.peek_token(n + 1).kind == .name {
311 if is_array_init_type_expr_field(p.peek_token(n + 1).lit) {
312 return true
313 }
314 n += 2
315 }
316 return false
317}
318
319fn (p &Parser) is_comptime_type_expr_arg() bool {
320 if p.tok.kind in [.key_typeof, .dollar] {
321 return true
322 }
323 if p.tok.kind == .lsbr && p.peek_tok.kind == .rsbr {
324 return p.is_comptime_type_selector_at(2)
325 }
326 return p.is_comptime_type_selector_at(0)
327}
328
329fn (mut p Parser) comptime_call_type_arg() ast.Expr {
330 arg_pos := p.tok.pos()
331 if p.is_comptime_type_expr_arg() {
332 old_inside_array_init_type_expr := p.inside_array_init_type_expr
333 p.inside_array_init_type_expr = true
334 expr := p.expr(0)
335 p.inside_array_init_type_expr = old_inside_array_init_type_expr
336 return expr
337 }
338 typ := p.parse_type()
339 return ast.TypeNode{
340 typ: typ
341 pos: arg_pos.extend(p.prev_tok.pos())
342 }
343}
344
345fn (mut p Parser) comptime_call() ast.ComptimeCall {
346 err_node := ast.ComptimeCall{
347 scope: unsafe { nil }
348 kind: .unknown
349 }
350 start_pos := p.tok.pos()
351 p.check(.dollar)
352 mut is_veb := false
353 if p.peek_tok.kind == .dot {
354 name := p.check_name()
355 if name != 'veb' {
356 p.error(error_msg)
357 return err_node
358 }
359 import_mods := p.ast_imports.map(it.mod)
360 if 'veb' !in import_mods {
361 p.error_with_pos('`\$veb` cannot be used without importing veb',
362 start_pos.extend(p.prev_tok.pos()))
363 return err_node
364 }
365 p.register_used_import('veb')
366 is_veb = true
367 p.check(.dot)
368 }
369 method_name := p.check_name()
370 if method_name !in supported_comptime_calls {
371 p.error(error_msg)
372 return err_node
373 }
374 is_embed_file := method_name == 'embed_file'
375 is_html := method_name == 'html'
376 p.check(.lpar)
377 arg_pos := p.tok.pos()
378 if method_name in ['env', 'pkgconfig'] {
379 s := p.tok.lit
380 p.check(.string)
381 p.check(.rpar)
382 is_env := method_name == 'env'
383 return ast.ComptimeCall{
384 scope: unsafe { nil }
385 method_name: method_name
386 kind: if is_env { .env } else { .pkgconfig }
387 args_var: s
388 env_pos: start_pos
389 pos: start_pos.extend(p.prev_tok.pos())
390 }
391 } else if method_name in ['compile_error', 'compile_warn'] {
392 mut s := ''
393 mut args := []ast.CallArg{}
394 if p.tok.kind == .string && p.peek_tok.kind == .rpar {
395 s = p.tok.lit
396 p.check(.string)
397 } else {
398 args << ast.CallArg{
399 expr: p.string_expr()
400 typ: ast.string_type
401 ct_expr: true
402 }
403 }
404 p.check(.rpar)
405 return ast.ComptimeCall{
406 scope: unsafe { nil }
407 method_name: method_name
408 kind: if method_name == 'compile_error' { .compile_error } else { .compile_warn }
409 args_var: s
410 env_pos: start_pos
411 pos: start_pos.extend(p.prev_tok.pos())
412 args: args
413 }
414 } else if method_name == 'res' {
415 mut has_args := false
416 mut type_index := ''
417 if p.tok.kind == .number {
418 has_args = true
419 type_index = p.tok.lit
420 p.check(.number)
421 }
422 p.check(.rpar)
423 if has_args {
424 return ast.ComptimeCall{
425 scope: unsafe { nil }
426 method_name: method_name
427 kind: .res
428 args_var: type_index
429 pos: start_pos.extend(p.prev_tok.pos())
430 }
431 }
432 return ast.ComptimeCall{
433 scope: unsafe { nil }
434 method_name: method_name
435 kind: .res
436 pos: start_pos.extend(p.prev_tok.pos())
437 }
438 } else if method_name == 'd' {
439 const_string := p.tok.lit
440 // const_name_pos := p.tok.pos()
441 p.check(.string)
442 p.check(.comma)
443 arg_expr := p.expr(0)
444 args := [
445 ast.CallArg{
446 expr: arg_expr
447 pos: p.tok.pos()
448 },
449 ]
450 p.check(.rpar)
451 return ast.ComptimeCall{
452 scope: unsafe { nil }
453 method_name: method_name
454 kind: .d
455 args_var: const_string
456 args: args
457 pos: start_pos.extend(p.prev_tok.pos())
458 }
459 } else if method_name in ['zero', 'new'] {
460 arg_expr := p.comptime_call_type_arg()
461 p.check(.rpar)
462 return ast.ComptimeCall{
463 scope: unsafe { nil }
464 method_name: method_name
465 kind: if method_name == 'zero' { .zero } else { .new }
466 args: [
467 ast.CallArg{
468 expr: arg_expr
469 pos: arg_pos
470 },
471 ]
472 pos: start_pos.extend(p.prev_tok.pos())
473 }
474 }
475 has_string_arg := p.tok.kind == .string
476 mut literal_string_param := if is_html && !has_string_arg { '' } else { p.tok.lit }
477 mut arg := ast.CallArg{}
478 if is_html && !(has_string_arg || p.tok.kind == .rpar) {
479 p.error('expecting `\$veb.html()` for a default template path or `\$veb.html("/path/to/template.html")`')
480 }
481 if is_html && p.tok.kind != .string {
482 // $veb.html() can have no arguments
483 } else {
484 arg_expr := p.expr(0)
485 if resolved_path := p.resolve_tmpl_path_expr(arg_expr) {
486 literal_string_param = resolved_path
487 }
488 arg = ast.CallArg{
489 expr: arg_expr
490 }
491 }
492 mut embed_compression_type := 'none'
493 if is_embed_file {
494 if p.tok.kind == .comma {
495 p.next()
496 p.check(.dot)
497 embed_compression_type = p.check_name()
498 }
499 }
500 p.check(.rpar)
501 // $embed_file('/path/to/file')
502 if is_embed_file {
503 p.register_auto_import('v.preludes.embed_file')
504 if embed_compression_type == 'zlib' {
505 p.register_auto_import('v.preludes.embed_file.zlib')
506 }
507 return ast.ComptimeCall{
508 scope: unsafe { nil }
509 method_name: method_name
510 kind: .embed_file
511 embed_file: ast.EmbeddedFile{
512 compression_type: embed_compression_type
513 }
514 args: [arg]
515 pos: start_pos.extend(p.prev_tok.pos())
516 }
517 }
518 // Compile veb html template to V code, parse that V code and embed the resulting V function
519 // that returns an html string.
520 fn_path := p.cur_fn_name.split('_')
521 fn_path_joined := fn_path.join(os.path_separator)
522 fn_name_html := '${p.cur_fn_name}.html'
523 compiled_vfile_path := os.real_path(p.scanner.file_path.replace('/', os.path_separator))
524 tmpl_path := if is_html && !has_string_arg {
525 '${fn_path.last()}.html'
526 } else {
527 mut resolved_path := literal_string_param.replace('/', os.path_separator)
528 resolved_path = p.resolve_tmpl_pseudo_variables(resolved_path, arg_pos) or {
529 return err_node
530 }
531 resolved_path
532 }
533 // Looking next to the veb program
534 dir := os.dir(compiled_vfile_path)
535 mut path := os.join_path_single(dir, fn_path_joined)
536 path += '.html'
537 if !is_html || has_string_arg {
538 if os.is_abs_path(tmpl_path) {
539 path = tmpl_path
540 } else {
541 path = os.join_path_single(dir, tmpl_path)
542 }
543 }
544 if !os.exists(path) {
545 if is_html {
546 if has_string_arg && !os.is_abs_path(tmpl_path) {
547 path = find_veb_template_relative_to_vmod(compiled_vfile_path, tmpl_path) or {
548 path
549 }
550 } else {
551 flat_path := os.join_path_single(dir, fn_name_html)
552 if os.exists(flat_path) {
553 path = flat_path
554 }
555 if !os.exists(path) {
556 // can be in `templates/`
557 path = os.join_path(dir, 'templates', fn_path_joined)
558 path += '.html'
559 }
560 if !os.exists(path) {
561 flat_template_path := os.join_path(dir, 'templates', fn_name_html)
562 if os.exists(flat_template_path) {
563 path = flat_template_path
564 }
565 }
566 if !os.exists(path) {
567 // Same-module subdirs and `base_url` can place route handlers below the module
568 // root while keeping `templates/` next to `v.mod`.
569 vmod_relative_paths := [
570 os.join_path('templates', fn_path_joined) + '.html',
571 os.join_path('templates', fn_name_html),
572 ]
573 path = find_existing_veb_template_in_vmod(compiled_vfile_path,
574 vmod_relative_paths) or { path }
575 }
576 }
577 }
578 if !os.exists(path) {
579 if p.pref.is_fmt {
580 return ast.ComptimeCall{
581 scope: unsafe { nil }
582 is_template: true
583 is_veb: is_veb
584 method_name: method_name
585 kind: if is_html { .html } else { .tmpl }
586 args_var: literal_string_param
587 args: [arg]
588 pos: start_pos.extend(p.prev_tok.pos())
589 }
590 }
591 if is_html {
592 p.error_with_pos('veb HTML template "${tmpl_path}" not found', arg_pos)
593 } else {
594 p.error_with_pos('template file "${tmpl_path}" not found', arg_pos)
595 }
596 return err_node
597 }
598 // println('path is now "$path"')
599 }
600 tmp_fn_name := p.cur_fn_name.replace('.', '__').to_lower() + start_pos.pos.str()
601 $if trace_comptime ? {
602 println('>>> compiling comptime template file "${path}" for ${tmp_fn_name}')
603 }
604 v_code := p.compile_template_file(path, tmp_fn_name)
605 $if print_veb_template_expansions ? {
606 lines := v_code.split('\n')
607 for i, line in lines {
608 println('${path}:${i + 1}: ${line}')
609 }
610 }
611 $if trace_comptime ? {
612 println('')
613 println('>>> template for ${path}:')
614 println(v_code)
615 println('>>> end of template END')
616 println('')
617 }
618 // the tmpl inherits all parent scopes. previous functionality was just to
619 // inherit the scope from which the comptime call was made and no parents.
620 // this is much simpler and allows access to globals. can be changed if needed.
621 p.open_scope()
622 defer {
623 p.close_scope()
624 }
625 mut file := parse_comptime(tmpl_path, v_code, mut p.table, p.pref, mut p.scope)
626 file.path = tmpl_path
627 template_call_stack := [
628 errors.CallStackItem{
629 file_path: p.file_path
630 pos: start_pos
631 },
632 ]
633 // Store call stack info for template errors
634 file.call_stack = template_call_stack
635 // Transfer template paths and line mapping from parser to file for error reporting
636 file.template_paths = p.template_paths
637 file.template_line_map = p.template_line_map
638 for i, err in file.errors {
639 mut file_path := err.file_path
640 mut line_nr := err.pos.line_nr
641 if err.pos.line_nr >= 0 && err.pos.line_nr < file.template_line_map.len {
642 line_info := file.template_line_map[err.pos.line_nr]
643 file_path = line_info.tmpl_path
644 line_nr = line_info.tmpl_line
645 }
646 file.errors[i] = errors.Error{
647 message: err.message
648 details: err.details
649 file_path: file_path
650 pos: token.Pos{
651 ...err.pos
652 line_nr: line_nr
653 }
654 reporter: err.reporter
655 call_stack: if err.call_stack.len > 0 { err.call_stack } else { template_call_stack }
656 }
657 }
658 for i, warn in file.warnings {
659 mut file_path := warn.file_path
660 mut line_nr := warn.pos.line_nr
661 if warn.pos.line_nr >= 0 && warn.pos.line_nr < file.template_line_map.len {
662 line_info := file.template_line_map[warn.pos.line_nr]
663 file_path = line_info.tmpl_path
664 line_nr = line_info.tmpl_line
665 }
666 file.warnings[i] = errors.Warning{
667 message: warn.message
668 details: warn.details
669 file_path: file_path
670 pos: token.Pos{
671 ...warn.pos
672 line_nr: line_nr
673 }
674 reporter: warn.reporter
675 call_stack: if warn.call_stack.len > 0 { warn.call_stack } else { template_call_stack }
676 }
677 }
678 for i, notice in file.notices {
679 mut file_path := notice.file_path
680 mut line_nr := notice.pos.line_nr
681 if notice.pos.line_nr >= 0 && notice.pos.line_nr < file.template_line_map.len {
682 line_info := file.template_line_map[notice.pos.line_nr]
683 file_path = line_info.tmpl_path
684 line_nr = line_info.tmpl_line
685 }
686 file.notices[i] = errors.Notice{
687 message: notice.message
688 details: notice.details
689 file_path: file_path
690 pos: token.Pos{
691 ...notice.pos
692 line_nr: line_nr
693 }
694 reporter: notice.reporter
695 call_stack: if notice.call_stack.len > 0 {
696 notice.call_stack
697 } else {
698 template_call_stack
699 }
700 }
701 }
702 return ast.ComptimeCall{
703 scope: unsafe { nil }
704 is_template: true
705 is_veb: is_veb
706 veb_tmpl: file
707 method_name: method_name
708 kind: if is_html { .html } else { .tmpl }
709 args_var: literal_string_param
710 args: [arg]
711 pos: start_pos.extend(p.prev_tok.pos())
712 }
713}
714
715fn (mut p Parser) comptime_for() ast.ComptimeFor {
716 // p.comptime_for() handles these special forms:
717 // `$for method in App.methods {`
718 // `$for val in App.values {`
719 // `$for field in App.fields {`
720 // `$for attr in App.attributes {`
721 // `$for variant in App.variants {`
722 // `$for variant in field.typ.variants {`
723 p.next()
724 p.check(.key_for)
725 var_pos := p.tok.pos()
726 val_var := p.check_name()
727 p.check(.key_in)
728 mut expr := ast.empty_expr
729 mut typ_pos := p.tok.pos()
730 lang := p.parse_language()
731 mut typ := ast.void_type
732
733 if p.tok.lit.len == 0 {
734 p.error('invalid expr, use `${p.peek_tok.lit}` instead')
735 return ast.ComptimeFor{}
736 }
737 if p.tok.lit[0].is_capital() || p.tok.lit in p.imports {
738 typ = p.parse_any_type(lang, false, true, false)
739 } else {
740 mut selector_expr := ast.Expr(p.ident(lang))
741 p.scope.mark_var_as_used((selector_expr as ast.Ident).name)
742 for p.tok.kind == .dot && p.peek_tok.kind == .name
743 && !is_supported_comptime_for_kind(p.peek_tok.lit) {
744 selector_expr = p.dot_expr(selector_expr)
745 if p.name_error {
746 return ast.ComptimeFor{}
747 }
748 if selector_expr !is ast.SelectorExpr {
749 p.error_with_pos('invalid expr, use a selector like `field.typ`',
750 selector_expr.pos())
751 return ast.ComptimeFor{}
752 }
753 }
754 expr = selector_expr
755 }
756 typ_pos = typ_pos.extend(p.prev_tok.pos())
757 p.check(.dot)
758 for_val := p.check_name()
759 mut kind := ast.ComptimeForKind.methods
760 p.open_scope()
761 defer {
762 p.close_scope()
763 }
764 match for_val {
765 'params' {
766 p.scope.register(ast.Var{
767 name: val_var
768 typ: p.table.find_type('FunctionParam')
769 pos: var_pos
770 })
771 kind = .params
772 }
773 'methods' {
774 p.scope.register(ast.Var{
775 name: val_var
776 typ: p.table.find_type('FunctionData')
777 pos: var_pos
778 })
779 }
780 'values' {
781 p.scope.register(ast.Var{
782 name: val_var
783 typ: p.table.find_type('EnumData')
784 pos: var_pos
785 })
786 kind = .values
787 }
788 'fields' {
789 p.scope.register(ast.Var{
790 name: val_var
791 typ: p.table.find_type('FieldData')
792 pos: var_pos
793 })
794 kind = .fields
795 }
796 'variants' {
797 p.scope.register(ast.Var{
798 name: val_var
799 typ: p.table.find_type('VariantData')
800 pos: var_pos
801 })
802 kind = .variants
803 }
804 'attributes' {
805 p.scope.register(ast.Var{
806 name: val_var
807 typ: p.table.find_type('VAttribute')
808 pos: var_pos
809 })
810 kind = .attributes
811 }
812 else {
813 p.error_with_pos('unknown kind `${for_val}`, available are: `methods`, `fields`, `values`, `variants`, `attributes` or `params`',
814 p.prev_tok.pos())
815 return ast.ComptimeFor{}
816 }
817 }
818
819 spos := p.tok.pos()
820 stmts := p.parse_block()
821 return ast.ComptimeFor{
822 val_var: val_var
823 stmts: stmts
824 kind: kind
825 typ: typ
826 expr: expr
827 typ_pos: typ_pos
828 scope: p.scope
829 pos: spos.extend(p.tok.pos())
830 }
831}
832
833// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens
834fn (mut p Parser) at() ast.AtExpr {
835 name := p.tok.lit
836 kind := match name {
837 '@FN' { token.AtKind.fn_name }
838 '@METHOD' { token.AtKind.method_name }
839 '@MOD' { token.AtKind.mod_name }
840 '@STRUCT' { token.AtKind.struct_name }
841 '@FILE' { token.AtKind.file_path }
842 '@DIR' { token.AtKind.file_dir }
843 '@LINE' { token.AtKind.line_nr }
844 '@FILE_LINE' { token.AtKind.file_path_line_nr }
845 '@LOCATION' { token.AtKind.location }
846 '@COLUMN' { token.AtKind.column_nr }
847 '@VCURRENTHASH' { token.AtKind.v_current_hash }
848 '@VHASH' { token.AtKind.vhash }
849 '@VMOD_FILE' { token.AtKind.vmod_file }
850 '@VEXE' { token.AtKind.vexe_path }
851 '@VEXEROOT' { token.AtKind.vexeroot_path }
852 '@VMODROOT' { token.AtKind.vmodroot_path }
853 '@VMODHASH' { token.AtKind.vmod_hash }
854 '@VROOT' { token.AtKind.vroot_path } // deprecated, use @VEXEROOT or @VMODROOT
855 '@BUILD_DATE' { token.AtKind.build_date }
856 '@BUILD_TIME' { token.AtKind.build_time }
857 '@BUILD_TIMESTAMP' { token.AtKind.build_timestamp }
858 '@OS' { token.AtKind.os }
859 '@CCOMPILER' { token.AtKind.ccompiler }
860 '@BACKEND' { token.AtKind.backend }
861 '@PLATFORM' { token.AtKind.platform }
862 else { token.AtKind.unknown }
863 }
864
865 expr := ast.AtExpr{
866 name: name
867 pos: p.tok.pos()
868 kind: kind
869 }
870 p.next()
871 return expr
872}
873
874fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
875 p.check(.dollar)
876 start_pos := p.prev_tok.pos()
877 if p.peek_tok.kind == .lpar {
878 method_pos := p.tok.pos()
879 method_name := p.check_name()
880 p.scope.mark_var_as_used(method_name)
881 // `app.$action()` (`action` is a string)
882 p.check(.lpar)
883 args := p.call_args()
884 p.check(.rpar)
885 mut or_kind := ast.OrKind.absent
886 mut or_pos := p.tok.pos()
887 mut or_stmts := []ast.Stmt{}
888 mut or_scope := ast.empty_scope
889 if p.tok.kind == .key_orelse {
890 // `$method() or {}``
891 or_kind = .block
892 or_stmts, or_pos, or_scope = p.or_block(.with_err_var)
893 }
894 return ast.ComptimeCall{
895 left: left
896 method_name: method_name
897 kind: .method
898 method_pos: method_pos
899 scope: p.scope
900 args_var: ''
901 args: args
902 pos: start_pos.extend(p.prev_tok.pos())
903 or_block: ast.OrExpr{
904 stmts: or_stmts
905 kind: or_kind
906 pos: or_pos
907 scope: or_scope
908 }
909 }
910 }
911 mut has_parens := false
912 if p.tok.kind == .lpar {
913 p.next()
914 has_parens = true
915 } else {
916 p.warn_with_pos('use brackets instead e.g. `s.$(field.name)` - run vfmt', p.tok.pos())
917 }
918 expr := p.expr(0)
919 if has_parens {
920 p.check(.rpar)
921 }
922 return ast.ComptimeSelector{
923 has_parens: has_parens
924 left: left
925 field_expr: expr
926 pos: start_pos.extend(p.prev_tok.pos())
927 or_block: ast.OrExpr{
928 stmts: []ast.Stmt{}
929 kind: if p.tok.kind == .question { .propagate_option } else { .absent }
930 scope: p.scope
931 pos: p.tok.pos()
932 }
933 }
934}
935