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 {` => `` + class := line.find_between('span.', '{').trim_space() + source.writeln('') + in_span = true + } else if state == .html && line.contains('.') && line.ends_with('{') { + // `.header {` => `
` + class := line.find_between('.', '{').trim_space() + source.writeln('
') + } else if state == .html && line.contains('#') && line.ends_with('{') { + // `#header {` => `