2021-01-18 13:20:06 +01:00
|
|
|
// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
|
2020-04-09 16:39:53 +02:00
|
|
|
// Use of this source code is governed by an MIT license
|
|
|
|
// that can be found in the LICENSE file.
|
2020-02-10 14:42:57 +01:00
|
|
|
module parser
|
2020-02-17 14:15:42 +01:00
|
|
|
|
2020-05-28 18:36:04 +02:00
|
|
|
import os
|
2020-04-25 17:49:16 +02:00
|
|
|
import v.ast
|
|
|
|
import v.pref
|
2020-05-25 08:17:36 +02:00
|
|
|
import v.table
|
2020-11-05 09:12:32 +01:00
|
|
|
import v.token
|
2020-06-06 21:36:24 +02:00
|
|
|
import vweb.tmpl
|
2020-02-17 14:15:42 +01:00
|
|
|
|
2021-01-14 15:20:11 +01:00
|
|
|
const (
|
2021-01-31 18:22:42 +01:00
|
|
|
supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file']
|
2021-01-14 15:20:11 +01:00
|
|
|
)
|
|
|
|
|
2020-04-09 16:39:53 +02:00
|
|
|
// // #include, #flag, #v
|
2020-04-23 01:16:58 +02:00
|
|
|
fn (mut p Parser) hash() ast.HashStmt {
|
2021-01-29 11:17:59 +01:00
|
|
|
pos := p.tok.position()
|
2020-10-15 10:58:01 +02:00
|
|
|
val := p.tok.lit
|
|
|
|
kind := val.all_before(' ')
|
2020-04-09 16:39:53 +02:00
|
|
|
p.next()
|
2020-11-29 20:23:37 +01:00
|
|
|
mut main_str := ''
|
2020-10-17 17:27:06 +02:00
|
|
|
mut msg := ''
|
|
|
|
content := val.all_after('$kind ').all_before('//')
|
|
|
|
if content.contains(' #') {
|
2020-11-29 20:23:37 +01:00
|
|
|
main_str = content.all_before(' #').trim_space()
|
2020-10-17 17:27:06 +02:00
|
|
|
msg = content.all_after(' #').trim_space()
|
|
|
|
} else {
|
2020-11-29 20:23:37 +01:00
|
|
|
main_str = content.trim_space()
|
2020-10-17 17:27:06 +02:00
|
|
|
msg = ''
|
|
|
|
}
|
2020-10-15 22:12:59 +02:00
|
|
|
// p.trace('a.v', 'kind: ${kind:-10s} | pos: ${pos:-45s} | hash: $val')
|
2020-04-09 16:39:53 +02:00
|
|
|
return ast.HashStmt{
|
2020-04-16 11:29:36 +02:00
|
|
|
mod: p.mod
|
2021-01-23 10:39:12 +01:00
|
|
|
source_file: p.file_name
|
2020-10-15 10:58:01 +02:00
|
|
|
val: val
|
|
|
|
kind: kind
|
2020-11-29 20:23:37 +01:00
|
|
|
main: main_str
|
2020-10-17 17:27:06 +02:00
|
|
|
msg: msg
|
2020-10-15 10:58:01 +02:00
|
|
|
pos: pos
|
2020-04-09 16:39:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-14 15:20:11 +01:00
|
|
|
fn (mut p Parser) comp_call() ast.ComptimeCall {
|
2021-01-30 15:24:16 +01:00
|
|
|
err_node := ast.ComptimeCall{
|
|
|
|
scope: 0
|
|
|
|
}
|
2020-05-27 03:33:37 +02:00
|
|
|
p.check(.dollar)
|
2021-01-31 18:22:42 +01:00
|
|
|
error_msg := 'only `\$tmpl()`, `\$env()`, `\$embed_file()` and `\$vweb.html()` comptime functions are supported right now'
|
2020-11-26 18:40:31 +01:00
|
|
|
if p.peek_tok.kind == .dot {
|
|
|
|
n := p.check_name() // skip `vweb.html()` TODO
|
|
|
|
if n != 'vweb' {
|
|
|
|
p.error(error_msg)
|
2021-01-30 15:24:16 +01:00
|
|
|
return err_node
|
2020-11-26 18:40:31 +01:00
|
|
|
}
|
|
|
|
p.check(.dot)
|
|
|
|
}
|
|
|
|
n := p.check_name() // (.name)
|
2021-01-25 10:26:20 +01:00
|
|
|
if n !in parser.supported_comptime_calls {
|
2020-11-26 18:40:31 +01:00
|
|
|
p.error(error_msg)
|
2021-01-30 15:24:16 +01:00
|
|
|
return err_node
|
2020-11-26 18:40:31 +01:00
|
|
|
}
|
2021-01-14 15:20:11 +01:00
|
|
|
is_embed_file := n == 'embed_file'
|
2020-11-26 18:40:31 +01:00
|
|
|
is_html := n == 'html'
|
2021-01-31 18:22:42 +01:00
|
|
|
// $env('ENV_VAR_NAME')
|
|
|
|
if n == 'env' {
|
|
|
|
p.check(.lpar)
|
|
|
|
spos := p.tok.position()
|
|
|
|
s := p.tok.lit
|
|
|
|
p.check(.string)
|
|
|
|
p.check(.rpar)
|
|
|
|
return ast.ComptimeCall{
|
|
|
|
scope: 0
|
|
|
|
method_name: n
|
|
|
|
args_var: s
|
|
|
|
is_env: true
|
|
|
|
env_pos: spos
|
|
|
|
}
|
|
|
|
}
|
2020-05-25 05:32:33 +02:00
|
|
|
p.check(.lpar)
|
2021-01-14 15:20:11 +01:00
|
|
|
spos := p.tok.position()
|
2020-11-26 18:40:31 +01:00
|
|
|
s := if is_html { '' } else { p.tok.lit }
|
|
|
|
if !is_html {
|
|
|
|
p.check(.string)
|
|
|
|
}
|
2020-05-25 05:32:33 +02:00
|
|
|
p.check(.rpar)
|
2021-01-31 18:22:42 +01:00
|
|
|
// $embed_file('/path/to/file')
|
2021-01-14 15:20:11 +01:00
|
|
|
if is_embed_file {
|
|
|
|
mut epath := s
|
|
|
|
// Validate that the epath exists, and that it is actually a file.
|
|
|
|
if epath == '' {
|
2021-01-31 18:22:42 +01:00
|
|
|
p.error_with_pos('supply a valid relative or absolute file path to the file to embed',
|
2021-01-14 15:20:11 +01:00
|
|
|
spos)
|
2021-01-30 15:24:16 +01:00
|
|
|
return err_node
|
2021-01-14 15:20:11 +01:00
|
|
|
}
|
|
|
|
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)
|
2021-01-30 15:24:16 +01:00
|
|
|
return err_node
|
2021-01-14 15:20:11 +01:00
|
|
|
}
|
|
|
|
if !os.is_file(epath) {
|
|
|
|
p.error_with_pos('"$epath" is not a file so it cannot be embedded',
|
|
|
|
spos)
|
2021-01-30 15:24:16 +01:00
|
|
|
return err_node
|
2021-01-14 15:20:11 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
epath = abs_path
|
|
|
|
}
|
|
|
|
}
|
2021-01-16 19:03:07 +01:00
|
|
|
p.register_auto_import('v.embed_file')
|
2021-01-14 15:20:11 +01:00
|
|
|
return ast.ComptimeCall{
|
2021-01-30 15:24:16 +01:00
|
|
|
scope: 0
|
2021-01-14 15:20:11 +01:00
|
|
|
is_embed: true
|
|
|
|
embed_file: ast.EmbeddedFile{
|
|
|
|
rpath: s
|
|
|
|
apath: epath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-06 21:36:24 +02:00
|
|
|
// Compile vweb html template to V code, parse that V code and embed the resulting V function
|
|
|
|
// that returns an html string.
|
2020-07-08 08:12:57 +02:00
|
|
|
fn_path := p.cur_fn_name.split('_')
|
2020-11-26 18:40:31 +01:00
|
|
|
tmpl_path := if is_html { '${fn_path.last()}.html' } else { s }
|
2020-06-10 18:00:06 +02:00
|
|
|
// Looking next to the vweb program
|
2021-01-21 11:09:19 +01:00
|
|
|
dir := os.dir(p.scanner.file_path.replace('/', os.path_separator))
|
|
|
|
mut path := os.join_path(dir, fn_path.join(os.path_separator))
|
2020-07-08 08:12:57 +02:00
|
|
|
path += '.html'
|
2021-01-21 11:09:19 +01:00
|
|
|
path = os.real_path(path)
|
2020-11-26 18:40:31 +01:00
|
|
|
if !is_html {
|
2021-02-19 11:39:15 +01:00
|
|
|
path = os.join_path(dir, tmpl_path)
|
2020-11-26 18:40:31 +01:00
|
|
|
}
|
2020-06-07 15:44:33 +02:00
|
|
|
if !os.exists(path) {
|
2020-06-10 18:00:06 +02:00
|
|
|
// can be in `templates/`
|
2020-11-26 18:40:31 +01:00
|
|
|
if is_html {
|
|
|
|
path = os.join_path(dir, 'templates', fn_path.join('/'))
|
|
|
|
path += '.html'
|
|
|
|
}
|
2020-06-07 15:44:33 +02:00
|
|
|
if !os.exists(path) {
|
2020-11-26 18:40:31 +01:00
|
|
|
if is_html {
|
|
|
|
p.error('vweb HTML template "$path" not found')
|
|
|
|
} else {
|
|
|
|
p.error('template file "$path" not found')
|
|
|
|
}
|
2021-01-30 15:24:16 +01:00
|
|
|
return err_node
|
2020-06-07 15:44:33 +02:00
|
|
|
}
|
|
|
|
// println('path is now "$path"')
|
|
|
|
}
|
2020-11-26 18:53:38 +01:00
|
|
|
if p.pref.is_verbose {
|
2020-11-26 18:40:31 +01:00
|
|
|
println('>>> compiling comptime template file "$path"')
|
2020-06-23 14:07:39 +02:00
|
|
|
}
|
2020-11-26 18:40:31 +01:00
|
|
|
tmp_fn_name := p.cur_fn_name.replace('.', '__')
|
|
|
|
v_code := tmpl.compile_file(path, tmp_fn_name)
|
2020-11-21 16:59:02 +01:00
|
|
|
$if print_vweb_template_expansions ? {
|
|
|
|
lines := v_code.split('\n')
|
|
|
|
for i, line in lines {
|
2020-11-21 18:04:31 +01:00
|
|
|
println('$path:${i + 1}: $line')
|
2020-11-21 16:59:02 +01:00
|
|
|
}
|
|
|
|
}
|
2020-06-07 12:26:45 +02:00
|
|
|
mut scope := &ast.Scope{
|
|
|
|
start_pos: 0
|
2020-06-09 09:08:11 +02:00
|
|
|
parent: p.global_scope
|
2020-06-07 12:26:45 +02:00
|
|
|
}
|
2020-11-26 18:53:38 +01:00
|
|
|
if p.pref.is_verbose {
|
2020-06-06 21:36:24 +02:00
|
|
|
println('\n\n')
|
2020-06-21 23:09:17 +02:00
|
|
|
println('>>> vweb template for $path:')
|
2020-06-06 21:36:24 +02:00
|
|
|
println(v_code)
|
|
|
|
println('>>> end of vweb template END')
|
|
|
|
println('\n\n')
|
|
|
|
}
|
2020-12-01 03:58:39 +01:00
|
|
|
mut file := parse_comptime(v_code, p.table, p.pref, scope, p.global_scope)
|
2021-01-17 06:24:03 +01:00
|
|
|
file = ast.File{
|
|
|
|
...file
|
2020-11-26 18:40:31 +01:00
|
|
|
path: tmpl_path
|
2020-07-03 15:10:39 +02:00
|
|
|
}
|
2020-06-07 13:26:47 +02:00
|
|
|
// copy vars from current fn scope into vweb_tmpl scope
|
|
|
|
for stmt in file.stmts {
|
|
|
|
if stmt is ast.FnDecl {
|
2020-11-26 18:40:31 +01:00
|
|
|
if stmt.name == 'main.vweb_tmpl_$tmp_fn_name' {
|
2020-12-12 09:01:12 +01:00
|
|
|
// mut tmpl_scope := file.scope.innermost(stmt.body_pos.pos)
|
|
|
|
mut tmpl_scope := stmt.scope
|
2020-06-07 13:26:47 +02:00
|
|
|
for _, obj in p.scope.objects {
|
|
|
|
if obj is ast.Var {
|
2020-07-09 17:14:14 +02:00
|
|
|
mut v := obj
|
|
|
|
v.pos = stmt.body_pos
|
2021-01-17 06:24:03 +01:00
|
|
|
tmpl_scope.register(ast.Var{
|
|
|
|
...v
|
|
|
|
is_used: true
|
|
|
|
})
|
2020-06-20 04:42:08 +02:00
|
|
|
// set the controller action var to used
|
2020-11-26 18:40:31 +01:00
|
|
|
// if it's unused in the template it will warn
|
2020-06-20 04:42:08 +02:00
|
|
|
v.is_used = true
|
2020-06-07 13:26:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-06-06 21:36:24 +02:00
|
|
|
return ast.ComptimeCall{
|
2021-01-30 15:24:16 +01:00
|
|
|
scope: 0
|
2020-06-06 21:36:24 +02:00
|
|
|
is_vweb: true
|
2020-06-07 12:26:45 +02:00
|
|
|
vweb_tmpl: file
|
2020-11-26 18:40:31 +01:00
|
|
|
method_name: n
|
2020-11-26 18:53:38 +01:00
|
|
|
args_var: s
|
2020-06-06 21:36:24 +02:00
|
|
|
}
|
2020-05-25 05:32:33 +02:00
|
|
|
}
|
|
|
|
|
2020-07-03 15:10:39 +02:00
|
|
|
fn (mut p Parser) comp_for() ast.CompFor {
|
2020-07-25 00:02:44 +02:00
|
|
|
// p.comp_for() handles these special forms:
|
|
|
|
// $for method in App(methods) {
|
|
|
|
// $for field in App(fields) {
|
2020-07-03 15:10:39 +02:00
|
|
|
p.next()
|
|
|
|
p.check(.key_for)
|
|
|
|
val_var := p.check_name()
|
|
|
|
p.check(.key_in)
|
2020-07-25 00:02:44 +02:00
|
|
|
lang := p.parse_language()
|
|
|
|
typ := p.parse_any_type(lang, false, false)
|
|
|
|
p.check(.dot)
|
|
|
|
for_val := p.check_name()
|
|
|
|
mut kind := ast.CompForKind.methods
|
|
|
|
if for_val == 'methods' {
|
2020-12-02 14:40:25 +01:00
|
|
|
p.scope.register(ast.Var{
|
2020-07-25 00:02:44 +02:00
|
|
|
name: val_var
|
|
|
|
typ: p.table.find_type_idx('FunctionData')
|
|
|
|
})
|
|
|
|
} else if for_val == 'fields' {
|
2020-12-02 14:40:25 +01:00
|
|
|
p.scope.register(ast.Var{
|
2020-07-25 00:02:44 +02:00
|
|
|
name: val_var
|
|
|
|
typ: p.table.find_type_idx('FieldData')
|
|
|
|
})
|
|
|
|
kind = .fields
|
|
|
|
} else {
|
|
|
|
p.error('unknown kind `$for_val`, available are: `methods` or `fields`')
|
2020-12-04 19:34:05 +01:00
|
|
|
return ast.CompFor{}
|
2020-07-25 00:02:44 +02:00
|
|
|
}
|
2020-11-04 12:34:12 +01:00
|
|
|
spos := p.tok.position()
|
2020-07-03 15:10:39 +02:00
|
|
|
stmts := p.parse_block()
|
|
|
|
return ast.CompFor{
|
|
|
|
val_var: val_var
|
|
|
|
stmts: stmts
|
2020-07-25 00:02:44 +02:00
|
|
|
kind: kind
|
2020-07-03 15:10:39 +02:00
|
|
|
typ: typ
|
2020-11-04 12:34:12 +01:00
|
|
|
pos: spos.extend(p.tok.position())
|
2020-07-03 15:10:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-05 09:12:32 +01:00
|
|
|
// @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 }
|
2021-02-05 15:30:58 +01:00
|
|
|
'@METHOD' { token.AtKind.method_name }
|
2020-11-05 09:12:32 +01:00
|
|
|
'@MOD' { token.AtKind.mod_name }
|
|
|
|
'@STRUCT' { token.AtKind.struct_name }
|
|
|
|
'@VEXE' { token.AtKind.vexe_path }
|
|
|
|
'@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 }
|
|
|
|
else { token.AtKind.unknown }
|
|
|
|
}
|
|
|
|
p.next()
|
|
|
|
return ast.AtExpr{
|
|
|
|
name: name
|
|
|
|
pos: p.tok.position()
|
|
|
|
kind: kind
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-05 15:11:43 +01:00
|
|
|
fn (mut p Parser) comptime_selector(left ast.Expr) ast.Expr {
|
2020-05-25 08:17:36 +02:00
|
|
|
p.check(.dollar)
|
2021-01-05 15:11:43 +01:00
|
|
|
if p.peek_tok.kind == .lpar {
|
2021-01-30 15:24:16 +01:00
|
|
|
method_pos := p.tok.position()
|
2021-01-05 15:11:43 +01:00
|
|
|
method_name := p.check_name()
|
2021-01-30 15:24:16 +01:00
|
|
|
p.mark_var_as_used(method_name)
|
2021-01-05 15:11:43 +01:00
|
|
|
// `app.$action()` (`action` is a string)
|
|
|
|
p.check(.lpar)
|
|
|
|
mut args_var := ''
|
|
|
|
if p.tok.kind == .name {
|
|
|
|
args_var = p.tok.lit
|
2021-01-31 15:28:23 +01:00
|
|
|
p.mark_var_as_used(args_var)
|
2021-01-05 15:11:43 +01:00
|
|
|
p.next()
|
2020-05-25 08:17:36 +02:00
|
|
|
}
|
2021-01-05 15:11:43 +01:00
|
|
|
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
|
2021-01-30 15:24:16 +01:00
|
|
|
method_pos: method_pos
|
|
|
|
scope: p.scope
|
2021-01-05 15:11:43 +01:00
|
|
|
args_var: args_var
|
2020-05-25 08:17:36 +02:00
|
|
|
}
|
|
|
|
}
|
2021-01-30 12:56:10 +01:00
|
|
|
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())
|
|
|
|
}
|
2021-01-05 15:11:43 +01:00
|
|
|
expr := p.expr(0)
|
|
|
|
if has_parens {
|
|
|
|
p.check(.rpar)
|
2020-07-03 15:10:39 +02:00
|
|
|
}
|
2021-01-05 15:11:43 +01:00
|
|
|
return ast.ComptimeSelector{
|
|
|
|
has_parens: has_parens
|
2020-05-27 03:20:22 +02:00
|
|
|
left: left
|
2021-01-05 15:11:43 +01:00
|
|
|
field_expr: expr
|
2020-05-27 03:20:22 +02:00
|
|
|
}
|
2020-05-25 08:17:36 +02:00
|
|
|
}
|