Skip to content

Commit 1ad4623

Browse files
authored
tmpl: move to v/parser (#9052)
1 parent 26138f9 commit 1ad4623

File tree

6 files changed

+223
-173
lines changed

6 files changed

+223
-173
lines changed
File renamed without changes.

vlib/v/checker/checker.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4101,7 +4101,7 @@ fn (mut c Checker) comptime_call(mut node ast.ComptimeCall) table.Type {
41014101
c2.check(node.vweb_tmpl)
41024102
mut i := 0 // tmp counter var for skipping first three tmpl vars
41034103
for k, _ in c2.file.scope.children[0].objects {
4104-
if i < 4 {
4104+
if i < 2 {
41054105
// Skip first three because they are tmpl vars see vlib/vweb/tmpl/tmpl.v
41064106
i++
41074107
continue

vlib/v/parser/comptime.v

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import v.ast
88
import v.pref
99
import v.table
1010
import v.token
11-
import vweb.tmpl
1211

1312
const (
1413
supported_comptime_calls = ['html', 'tmpl', 'env', 'embed_file']
@@ -168,7 +167,7 @@ fn (mut p Parser) comp_call() ast.ComptimeCall {
168167
$if trace_comptime ? {
169168
println('>>> compiling comptime template file "$path" for $tmp_fn_name')
170169
}
171-
v_code := tmpl.compile_file(path, tmp_fn_name)
170+
v_code := p.compile_template_file(path, tmp_fn_name)
172171
$if print_vweb_template_expansions ? {
173172
lines := v_code.split('\n')
174173
for i, line in lines {
@@ -181,9 +180,9 @@ fn (mut p Parser) comp_call() ast.ComptimeCall {
181180
}
182181
$if trace_comptime ? {
183182
println('')
184-
println('>>> vweb template for $path:')
183+
println('>>> template for $path:')
185184
println(v_code)
186-
println('>>> end of vweb template END')
185+
println('>>> end of template END')
187186
println('')
188187
}
189188
mut file := parse_comptime(v_code, p.table, p.pref, scope, p.global_scope)

vlib/v/parser/parser.v

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -979,6 +979,31 @@ pub fn (mut p Parser) error_with_pos(s string, pos token.Position) {
979979
}
980980
}
981981

982+
pub fn (mut p Parser) error_with_error(error errors.Error) {
983+
if p.pref.fatal_errors {
984+
exit(1)
985+
}
986+
mut kind := 'error:'
987+
if p.pref.output_mode == .stdout {
988+
if p.pref.is_verbose {
989+
print_backtrace()
990+
kind = 'parser error:'
991+
}
992+
ferror := util.formatted_error(kind, error.message, error.file_path, error.pos)
993+
eprintln(ferror)
994+
exit(1)
995+
} else {
996+
p.errors << error
997+
}
998+
if p.pref.output_mode == .silent {
999+
// Normally, parser errors mean that the parser exits immediately, so there can be only 1 parser error.
1000+
// In the silent mode however, the parser continues to run, even though it would have stopped. Some
1001+
// of the parser logic does not expect that, and may loop forever.
1002+
// The p.next() here is needed, so the parser is more robust, and *always* advances, even in the -silent mode.
1003+
p.next()
1004+
}
1005+
}
1006+
9821007
pub fn (mut p Parser) warn_with_pos(s string, pos token.Position) {
9831008
if p.pref.warns_are_errors {
9841009
p.error_with_pos(s, pos)

vlib/v/parser/tmpl.v

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright (c) 2019-2021 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.
4+
module parser
5+
6+
import v.token
7+
import v.errors
8+
import os
9+
import strings
10+
11+
const tmpl_str_start = "sb.write_string('"
12+
13+
const tmpl_str_end = "' ) "
14+
15+
enum State {
16+
html
17+
css // <style>
18+
js // <script>
19+
// span // span.{
20+
}
21+
22+
// compile_file compiles the content of a file by the given path as a template
23+
pub fn (mut p Parser) compile_template_file(template_file string, fn_name string) string {
24+
mut lines := os.read_lines(template_file) or {
25+
p.error('reading from $template_file failed')
26+
return ''
27+
}
28+
basepath := os.dir(template_file)
29+
lstartlength := lines.len * 30
30+
mut source := strings.new_builder(1000)
31+
source.writeln('
32+
import strings
33+
// === vweb html template ===
34+
fn vweb_tmpl_${fn_name}() {
35+
mut sb := strings.new_builder($lstartlength)\n
36+
37+
')
38+
source.write_string(parser.tmpl_str_start)
39+
mut state := State.html
40+
mut in_span := false
41+
mut end_of_line_pos := 0
42+
mut start_of_line_pos := 0
43+
mut tline_number := -1 // keep the original line numbers, even after insert/delete ops on lines; `i` changes
44+
for i := 0; i < lines.len; i++ {
45+
oline := lines[i]
46+
tline_number++
47+
start_of_line_pos = end_of_line_pos
48+
end_of_line_pos += oline.len + 1
49+
$if trace_tmpl ? {
50+
eprintln('>>> tfile: $template_file, spos: ${start_of_line_pos:6}, epos:${end_of_line_pos:6}, fi: ${tline_number:5}, i: ${i:5}, line: $oline')
51+
}
52+
line := oline.trim_space()
53+
if line == '<style>' {
54+
state = .css
55+
} else if line == '</style>' {
56+
state = .html
57+
} else if line == '<script>' {
58+
state = .js
59+
} else if line == '</script>' {
60+
state = .html
61+
}
62+
if line.contains('@header') {
63+
position := line.index('@header') or { 0 }
64+
p.error_with_error(errors.Error{
65+
message: "Please use @include 'header' instead of @header (deprecated)"
66+
file_path: template_file
67+
pos: token.Position{
68+
len: '@header'.len
69+
line_nr: tline_number
70+
pos: start_of_line_pos + position
71+
last_line: lines.len
72+
}
73+
reporter: .parser
74+
})
75+
} else if line.contains('@footer') {
76+
position := line.index('@footer') or { 0 }
77+
p.error_with_error(errors.Error{
78+
message: "Please use @include 'footer' instead of @footer (deprecated)"
79+
file_path: template_file
80+
pos: token.Position{
81+
len: '@footer'.len
82+
line_nr: tline_number
83+
pos: start_of_line_pos + position
84+
last_line: lines.len
85+
}
86+
reporter: .parser
87+
})
88+
}
89+
if line.contains('@include ') {
90+
lines.delete(i)
91+
mut file_name := line.split("'")[1]
92+
mut file_ext := os.file_ext(file_name)
93+
if file_ext == '' {
94+
file_ext = '.html'
95+
}
96+
file_name = file_name.replace(file_ext, '')
97+
// relative path, starting with the current folder
98+
mut templates_folder := os.real_path(basepath)
99+
if file_name.contains('/') && file_name.starts_with('/') {
100+
// an absolute path
101+
templates_folder = ''
102+
}
103+
file_path := os.real_path(os.join_path(templates_folder, '$file_name$file_ext'))
104+
$if trace_tmpl ? {
105+
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"')
106+
}
107+
file_content := os.read_file(file_path) or {
108+
position := line.index('@include ') or { 0 } + '@include '.len
109+
p.error_with_error(errors.Error{
110+
message: 'Reading file $file_name from path: $file_path failed'
111+
details: "Failed to @include '$file_name'"
112+
file_path: template_file
113+
pos: token.Position{
114+
len: '@include '.len + file_name.len
115+
line_nr: tline_number
116+
pos: start_of_line_pos + position
117+
last_line: lines.len
118+
}
119+
reporter: .parser
120+
})
121+
''
122+
}
123+
file_splitted := file_content.split_into_lines().reverse()
124+
for f in file_splitted {
125+
tline_number--
126+
lines.insert(i, f)
127+
}
128+
i--
129+
} else if line.contains('@js ') {
130+
pos := line.index('@js') or { continue }
131+
source.write_string('<script src="')
132+
source.write_string(line[pos + 5..line.len - 1])
133+
source.writeln('"></script>')
134+
} else if line.contains('@css ') {
135+
pos := line.index('@css') or { continue }
136+
source.write_string('<link href="')
137+
source.write_string(line[pos + 6..line.len - 1])
138+
source.writeln('" rel="stylesheet" type="text/css">')
139+
} else if line.contains('@if ') {
140+
source.writeln(parser.tmpl_str_end)
141+
pos := line.index('@if') or { continue }
142+
source.writeln('if ' + line[pos + 4..] + '{')
143+
source.writeln(parser.tmpl_str_start)
144+
} else if line.contains('@end') {
145+
// Remove new line byte
146+
source.go_back(1)
147+
source.writeln(parser.tmpl_str_end)
148+
source.writeln('}')
149+
source.writeln(parser.tmpl_str_start)
150+
} else if line.contains('@else') {
151+
// Remove new line byte
152+
source.go_back(1)
153+
source.writeln(parser.tmpl_str_end)
154+
source.writeln(' } else { ')
155+
source.writeln(parser.tmpl_str_start)
156+
} else if line.contains('@for') {
157+
source.writeln(parser.tmpl_str_end)
158+
pos := line.index('@for') or { continue }
159+
source.writeln('for ' + line[pos + 4..] + '{')
160+
source.writeln(parser.tmpl_str_start)
161+
} else if state == .html && line.contains('span.') && line.ends_with('{') {
162+
// `span.header {` => `<span class='header'>`
163+
class := line.find_between('span.', '{').trim_space()
164+
source.writeln('<span class="$class">')
165+
in_span = true
166+
} else if state == .html && line.contains('.') && line.ends_with('{') {
167+
// `.header {` => `<div class='header'>`
168+
class := line.find_between('.', '{').trim_space()
169+
source.writeln('<div class="$class">')
170+
} else if state == .html && line.contains('#') && line.ends_with('{') {
171+
// `#header {` => `<div id='header'>`
172+
class := line.find_between('#', '{').trim_space()
173+
source.writeln('<div id="$class">')
174+
} else if state == .html && line == '}' {
175+
if in_span {
176+
source.writeln('</span>')
177+
in_span = false
178+
} else {
179+
source.writeln('</div>')
180+
}
181+
} else {
182+
// HTML, may include `@var`
183+
// escaped by cgen, unless it's a `vweb.RawHtml` string
184+
source.writeln(line.replace('@', '$').replace('$$', '@').replace('.$', '.@').replace("'",
185+
"\\'"))
186+
}
187+
}
188+
source.writeln(parser.tmpl_str_end)
189+
source.writeln('_tmpl_res_$fn_name := sb.str() ')
190+
source.writeln('}')
191+
source.writeln('// === end of vweb html template ===')
192+
result := source.str()
193+
return result
194+
}

0 commit comments

Comments
 (0)