aboutsummaryrefslogtreecommitdiff
path: root/v_windows/v/vlib/v/parser/comptime.v
diff options
context:
space:
mode:
Diffstat (limited to 'v_windows/v/vlib/v/parser/comptime.v')
-rw-r--r--v_windows/v/vlib/v/parser/comptime.v359
1 files changed, 359 insertions, 0 deletions
diff --git a/v_windows/v/vlib/v/parser/comptime.v b/v_windows/v/vlib/v/parser/comptime.v
new file mode 100644
index 0000000..b85042d
--- /dev/null
+++ b/v_windows/v/vlib/v/parser/comptime.v
@@ -0,0 +1,359 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module parser
+
+import os
+import v.ast
+import v.pref
+import v.token
+
+const (
+ supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file', 'pkgconfig']
+)
+
+// // #include, #flag, #v
+fn (mut p Parser) hash() ast.HashStmt {
+ pos := p.tok.position()
+ val := p.tok.lit
+ kind := val.all_before(' ')
+ p.next()
+ mut main_str := ''
+ mut msg := ''
+ content := val.all_after('$kind ').all_before('//')
+ if content.contains(' #') {
+ main_str = content.all_before(' #').trim_space()
+ msg = content.all_after(' #').trim_space()
+ } else {
+ main_str = content.trim_space()
+ msg = ''
+ }
+ return ast.HashStmt{
+ mod: p.mod
+ source_file: p.file_name
+ val: val
+ kind: kind
+ main: main_str
+ msg: msg
+ pos: pos
+ }
+}
+
+fn (mut p Parser) comp_call() ast.ComptimeCall {
+ err_node := ast.ComptimeCall{
+ scope: 0
+ }
+ p.check(.dollar)
+ start_pos := p.prev_tok.position()
+ error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()`, `\$pkgconfig()` and `\$vweb.html()` comptime functions are supported right now'
+ if p.peek_tok.kind == .dot {
+ name := p.check_name() // skip `vweb.html()` TODO
+ if name != 'vweb' {
+ p.error(error_msg)
+ return err_node
+ }
+ p.check(.dot)
+ }
+ method_name := p.check_name() // (.name)
+ if method_name !in parser.supported_comptime_calls {
+ p.error(error_msg)
+ return err_node
+ }
+ is_embed_file := method_name == 'embed_file'
+ is_html := method_name == 'html'
+ // $env('ENV_VAR_NAME')
+ p.check(.lpar)
+ spos := p.tok.position()
+ if method_name == 'env' {
+ s := p.tok.lit
+ p.check(.string)
+ p.check(.rpar)
+ return ast.ComptimeCall{
+ scope: 0
+ method_name: method_name
+ args_var: s
+ is_env: true
+ env_pos: spos
+ pos: spos.extend(p.prev_tok.position())
+ }
+ }
+ if method_name == 'pkgconfig' {
+ s := p.tok.lit
+ p.check(.string)
+ p.check(.rpar)
+ return ast.ComptimeCall{
+ scope: 0
+ method_name: method_name
+ args_var: s
+ is_pkgconfig: true
+ env_pos: spos
+ pos: spos.extend(p.prev_tok.position())
+ }
+ }
+ literal_string_param := if is_html { '' } else { p.tok.lit }
+ path_of_literal_string_param := literal_string_param.replace('/', os.path_separator)
+ if !is_html {
+ p.check(.string)
+ }
+ p.check(.rpar)
+ // $embed_file('/path/to/file')
+ if is_embed_file {
+ mut epath := path_of_literal_string_param
+ // Validate that the epath exists, and that it is actually a file.
+ if epath == '' {
+ p.error_with_pos('supply a valid relative or absolute file path to the file to embed',
+ spos)
+ return err_node
+ }
+ if !p.pref.is_fmt {
+ abs_path := os.real_path(epath)
+ // check absolute path first
+ if !os.exists(abs_path) {
+ // ... look relative to the source file:
+ epath = os.real_path(os.join_path(os.dir(p.file_name), epath))
+ if !os.exists(epath) {
+ p.error_with_pos('"$epath" does not exist so it cannot be embedded',
+ spos)
+ return err_node
+ }
+ if !os.is_file(epath) {
+ p.error_with_pos('"$epath" is not a file so it cannot be embedded',
+ spos)
+ return err_node
+ }
+ } else {
+ epath = abs_path
+ }
+ }
+ p.register_auto_import('v.embed_file')
+ return ast.ComptimeCall{
+ scope: 0
+ is_embed: true
+ embed_file: ast.EmbeddedFile{
+ rpath: literal_string_param
+ apath: epath
+ }
+ pos: start_pos.extend(p.prev_tok.position())
+ }
+ }
+ // Compile vweb html template to V code, parse that V code and embed the resulting V function
+ // that returns an html string.
+ fn_path := p.cur_fn_name.split('_')
+ fn_path_joined := fn_path.join(os.path_separator)
+ compiled_vfile_path := os.real_path(p.scanner.file_path.replace('/', os.path_separator))
+ tmpl_path := if is_html { '${fn_path.last()}.html' } else { path_of_literal_string_param }
+ // Looking next to the vweb program
+ dir := os.dir(compiled_vfile_path)
+ mut path := os.join_path(dir, fn_path_joined)
+ path += '.html'
+ path = os.real_path(path)
+ if !is_html {
+ path = os.join_path(dir, tmpl_path)
+ }
+ if !os.exists(path) {
+ if is_html {
+ // can be in `templates/`
+ path = os.join_path(dir, 'templates', fn_path_joined)
+ path += '.html'
+ }
+ if !os.exists(path) {
+ if p.pref.is_fmt {
+ return ast.ComptimeCall{
+ scope: 0
+ is_vweb: true
+ method_name: method_name
+ args_var: literal_string_param
+ pos: start_pos.extend(p.prev_tok.position())
+ }
+ }
+ if is_html {
+ p.error('vweb HTML template "$path" not found')
+ } else {
+ p.error('template file "$path" not found')
+ }
+ return err_node
+ }
+ // println('path is now "$path"')
+ }
+ tmp_fn_name := p.cur_fn_name.replace('.', '__')
+ $if trace_comptime ? {
+ println('>>> compiling comptime template file "$path" for $tmp_fn_name')
+ }
+ v_code := p.compile_template_file(path, tmp_fn_name)
+ $if print_vweb_template_expansions ? {
+ lines := v_code.split('\n')
+ for i, line in lines {
+ println('$path:${i + 1}: $line')
+ }
+ }
+ mut scope := &ast.Scope{
+ start_pos: 0
+ parent: p.table.global_scope
+ }
+ $if trace_comptime ? {
+ println('')
+ println('>>> template for $path:')
+ println(v_code)
+ println('>>> end of template END')
+ println('')
+ }
+ mut file := parse_comptime(v_code, p.table, p.pref, scope)
+ file.path = tmpl_path
+ // copy vars from current fn scope into vweb_tmpl scope
+ for stmt in file.stmts {
+ if stmt is ast.FnDecl {
+ if stmt.name == 'main.vweb_tmpl_$tmp_fn_name' {
+ // mut tmpl_scope := file.scope.innermost(stmt.body_pos.pos)
+ mut tmpl_scope := stmt.scope
+ for _, obj in p.scope.objects {
+ if obj is ast.Var {
+ mut v := obj
+ v.pos = stmt.body_pos
+ tmpl_scope.register(ast.Var{
+ ...v
+ is_used: true
+ })
+ // set the controller action var to used
+ // if it's unused in the template it will warn
+ v.is_used = true
+ }
+ }
+ break
+ }
+ }
+ }
+ return ast.ComptimeCall{
+ scope: 0
+ is_vweb: true
+ vweb_tmpl: file
+ method_name: method_name
+ args_var: literal_string_param
+ pos: start_pos.extend(p.prev_tok.position())
+ }
+}
+
+fn (mut p Parser) comp_for() ast.CompFor {
+ // p.comp_for() handles these special forms:
+ // $for method in App(methods) {
+ // $for field in App(fields) {
+ p.next()
+ p.check(.key_for)
+ var_pos := p.tok.position()
+ val_var := p.check_name()
+ p.check(.key_in)
+ mut typ_pos := p.tok.position()
+ lang := p.parse_language()
+ typ := p.parse_any_type(lang, false, false)
+ typ_pos = typ_pos.extend(p.prev_tok.position())
+ p.check(.dot)
+ for_val := p.check_name()
+ mut kind := ast.CompForKind.methods
+ p.open_scope()
+ if for_val == 'methods' {
+ p.scope.register(ast.Var{
+ name: val_var
+ typ: p.table.find_type_idx('FunctionData')
+ pos: var_pos
+ })
+ } else if for_val == 'fields' {
+ p.scope.register(ast.Var{
+ name: val_var
+ typ: p.table.find_type_idx('FieldData')
+ pos: var_pos
+ })
+ kind = .fields
+ } else if for_val == 'attributes' {
+ p.scope.register(ast.Var{
+ name: val_var
+ typ: p.table.find_type_idx('StructAttribute')
+ pos: var_pos
+ })
+ kind = .attributes
+ } else {
+ p.error_with_pos('unknown kind `$for_val`, available are: `methods`, `fields` or `attributes`',
+ p.prev_tok.position())
+ return ast.CompFor{}
+ }
+ spos := p.tok.position()
+ stmts := p.parse_block()
+ p.close_scope()
+ return ast.CompFor{
+ val_var: val_var
+ stmts: stmts
+ kind: kind
+ typ: typ
+ typ_pos: typ_pos
+ pos: spos.extend(p.tok.position())
+ }
+}
+
+// @FN, @STRUCT, @MOD etc. See full list in token.valid_at_tokens
+fn (mut p Parser) at() ast.AtExpr {
+ name := p.tok.lit
+ kind := match name {
+ '@FN' { token.AtKind.fn_name }
+ '@METHOD' { token.AtKind.method_name }
+ '@MOD' { token.AtKind.mod_name }
+ '@STRUCT' { token.AtKind.struct_name }
+ '@FILE' { token.AtKind.file_path }
+ '@LINE' { token.AtKind.line_nr }
+ '@COLUMN' { token.AtKind.column_nr }
+ '@VHASH' { token.AtKind.vhash }
+ '@VMOD_FILE' { token.AtKind.vmod_file }
+ '@VEXE' { token.AtKind.vexe_path }
+ '@VEXEROOT' { token.AtKind.vexeroot_path }
+ '@VMODROOT' { token.AtKind.vmodroot_path }
+ '@VROOT' { token.AtKind.vroot_path } // deprecated, use @VEXEROOT or @VMODROOT
+ else { token.AtKind.unknown }
+ }
+ p.next()
+ return ast.AtExpr{
+ name: name
+ pos: p.tok.position()
+ kind: kind
+ }
+}
+
+fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
+ p.check(.dollar)
+ start_pos := p.prev_tok.position()
+ if p.peek_tok.kind == .lpar {
+ method_pos := p.tok.position()
+ method_name := p.check_name()
+ p.mark_var_as_used(method_name)
+ // `app.$action()` (`action` is a string)
+ p.check(.lpar)
+ args := p.call_args()
+ p.check(.rpar)
+ if p.tok.kind == .key_orelse {
+ p.check(.key_orelse)
+ p.check(.lcbr)
+ }
+ return ast.ComptimeCall{
+ left: left
+ method_name: method_name
+ method_pos: method_pos
+ scope: p.scope
+ args_var: ''
+ args: args
+ pos: start_pos.extend(p.prev_tok.position())
+ }
+ }
+ mut has_parens := false
+ if p.tok.kind == .lpar {
+ p.check(.lpar)
+ has_parens = true
+ } else {
+ p.warn_with_pos('use brackets instead e.g. `s.$(field.name)` - run vfmt', p.tok.position())
+ }
+ expr := p.expr(0)
+ if has_parens {
+ p.check(.rpar)
+ }
+ return ast.ComptimeSelector{
+ has_parens: has_parens
+ left: left
+ field_expr: expr
+ pos: start_pos.extend(p.prev_tok.position())
+ }
+}