diff --git a/vlib/vweb/tmpl/README.md b/vlib/v/TEMPLATES.md
similarity index 100%
rename from vlib/vweb/tmpl/README.md
rename to vlib/v/TEMPLATES.md
diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v
index 54cc35f0e8..48f547688f 100644
--- a/vlib/v/checker/checker.v
+++ b/vlib/v/checker/checker.v
@@ -4101,7 +4101,7 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type {
c2.check(node.vweb_tmpl)
mut i := 0 // tmp counter var for skipping first three tmpl vars
for k, _ in c2.file.scope.children[0].objects {
- if i < 4 {
+ if i < 2 {
// Skip first three because they are tmpl vars see vlib/vweb/tmpl/tmpl.v
i++
continue
diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v
index d2adb61283..619eb1e660 100644
--- a/vlib/v/parser/comptime.v
+++ b/vlib/v/parser/comptime.v
@@ -8,7 +8,6 @@ import v.ast
import v.pref
import v.table
import v.token
-import vweb.tmpl
const (
supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file']
@@ -168,7 +167,7 @@ fn (mut p Parser) comp_call() ast.ComptimeCall {
$if trace_comptime ? {
println('>>> compiling comptime template file "$path" for $tmp_fn_name')
}
- v_code := tmpl.compile_file(path, 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 {
@@ -181,9 +180,9 @@ fn (mut p Parser) comp_call() ast.ComptimeCall {
}
$if trace_comptime ? {
println('')
- println('>>> vweb template for $path:')
+ println('>>> template for $path:')
println(v_code)
- println('>>> end of vweb template END')
+ println('>>> end of template END')
println('')
}
mut file := parse_comptime(v_code, p.table, p.pref, scope, p.global_scope)
diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v
index 988ab1837a..da228f8c9b 100644
--- a/vlib/v/parser/parser.v
+++ b/vlib/v/parser/parser.v
@@ -979,6 +979,31 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) {
}
}
+pub fn (mut p Parser) error_with_error(error errors.Error) {
+ if p.pref.fatal_errors {
+ exit(1)
+ }
+ mut kind := 'error:'
+ if p.pref.output_mode == .stdout {
+ if p.pref.is_verbose {
+ print_backtrace()
+ kind = 'parser error:'
+ }
+ ferror := util.formatted_error(kind, error.message, error.file_path, error.pos)
+ eprintln(ferror)
+ exit(1)
+ } else {
+ p.errors << error
+ }
+ if p.pref.output_mode == .silent {
+ // Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error.
+ // In the silent mode however, the parser continues to run, even though it would have stopped. Some
+ // of the parser logic does not expect that, and may loop forever.
+ // The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode.
+ p.next()
+ }
+}
+
pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) {
if p.pref.warns_are_errors {
p.error_with_pos(s, pos)
diff --git a/vlib/v/parser/tmpl.v b/vlib/v/parser/tmpl.v
new file mode 100644
index 0000000000..22956cfe93
--- /dev/null
+++ b/vlib/v/parser/tmpl.v
@@ -0,0 +1,194 @@
+// Copyright (c) 2019-2021 Alexander Medvednikov. All rights reserved.
+// Use of this source code is governed by an MIT license
+// that can be found in the LICENSE file.
+module parser
+
+import v.token
+import v.errors
+import os
+import strings
+
+const tmpl_str_start = "sb.write_string('"
+
+const tmpl_str_end = "' ) "
+
+enum State {
+ html
+ css // ' {
+ state = .html
+ } else if line == '' {
+ state = .html
+ }
+ if line.contains('@header') {
+ position := line.index('@header') or { 0 }
+ p.error_with_error(errors.Error{
+ message: "Please use @include 'header' instead of @header (deprecated)"
+ file_path: template_file
+ pos: token.Position{
+ len: '@header'.len
+ line_nr: tline_number
+ pos: start_of_line_pos + position
+ last_line: lines.len
+ }
+ reporter: .parser
+ })
+ } else if line.contains('@footer') {
+ position := line.index('@footer') or { 0 }
+ p.error_with_error(errors.Error{
+ message: "Please use @include 'footer' instead of @footer (deprecated)"
+ file_path: template_file
+ pos: token.Position{
+ len: '@footer'.len
+ line_nr: tline_number
+ pos: start_of_line_pos + position
+ last_line: lines.len
+ }
+ reporter: .parser
+ })
+ }
+ if line.contains('@include ') {
+ lines.delete(i)
+ mut file_name := line.split("'")[1]
+ mut file_ext := os.file_ext(file_name)
+ if file_ext == '' {
+ file_ext = '.html'
+ }
+ file_name = file_name.replace(file_ext, '')
+ // relative path, starting with the current folder
+ mut templates_folder := os.real_path(basepath)
+ if file_name.contains('/') && file_name.starts_with('/') {
+ // an absolute path
+ templates_folder = ''
+ }
+ file_path := os.real_path(os.join_path(templates_folder, '$file_name$file_ext'))
+ $if trace_tmpl ? {
+ eprintln('>>> basepath: "$basepath" , template_file: "$template_file" , fn_name: "$fn_name" , @include line: "$line" , file_name: "$file_name" , file_ext: "$file_ext" , templates_folder: "$templates_folder" , file_path: "$file_path"')
+ }
+ file_content := os.read_file(file_path) or {
+ position := line.index('@include ') or { 0 } + '@include '.len
+ p.error_with_error(errors.Error{
+ message: 'Reading file $file_name from path: $file_path failed'
+ details: "Failed to @include '$file_name'"
+ file_path: template_file
+ pos: token.Position{
+ len: '@include '.len + file_name.len
+ line_nr: tline_number
+ pos: start_of_line_pos + position
+ last_line: lines.len
+ }
+ reporter: .parser
+ })
+ ''
+ }
+ file_splitted := file_content.split_into_lines().reverse()
+ for f in file_splitted {
+ tline_number--
+ lines.insert(i, f)
+ }
+ i--
+ } else if line.contains('@js ') {
+ pos := line.index('@js') or { continue }
+ source.write_string('')
+ } else if line.contains('@css ') {
+ pos := line.index('@css') or { continue }
+ source.write_string('')
+ } else if line.contains('@if ') {
+ source.writeln(parser.tmpl_str_end)
+ pos := line.index('@if') or { continue }
+ source.writeln('if ' + line[pos + 4..] + '{')
+ source.writeln(parser.tmpl_str_start)
+ } else if line.contains('@end') {
+ // Remove new line byte
+ source.go_back(1)
+ source.writeln(parser.tmpl_str_end)
+ source.writeln('}')
+ source.writeln(parser.tmpl_str_start)
+ } else if line.contains('@else') {
+ // Remove new line byte
+ source.go_back(1)
+ source.writeln(parser.tmpl_str_end)
+ source.writeln(' } else { ')
+ source.writeln(parser.tmpl_str_start)
+ } else if line.contains('@for') {
+ source.writeln(parser.tmpl_str_end)
+ pos := line.index('@for') or { continue }
+ source.writeln('for ' + line[pos + 4..] + '{')
+ source.writeln(parser.tmpl_str_start)
+ } else if state == .html && line.contains('span.') && line.ends_with('{') {
+ // `span.header {` => `