// Template-level lexer.
//
// Splits a template string into Text / Variable / Tag tokens. Variable
// expressions (`{{ }}`), block tags (`{% %}`), and comments (`{# #}`) are
// recognized; everything else is literal text. The expression/tag contents are
// returned verbatim (trimmed); parsing them into structured AST happens later.
///|
/// Push the accumulated text buffer as a `Text` token and reset it, unless empty.
fn flush_text(text : StringBuilder, tokens : Array[Token]) -> Unit {
if !text.is_empty() {
tokens.push(Text(text.to_string()))
text.reset()
}
}
///|
/// Lex a template string into top-level tokens.
pub fn lex(src : String) -> Array[Token] {
let chars = src.to_array()
let n = chars.length()
let tokens : Array[Token] = []
let mut i = 0
let text = StringBuilder::new()
while i < n {
if chars[i] == '{' && i + 1 < n {
match chars[i + 1] {
'{' => {
// variable: scan until }}
flush_text(text, tokens)
let buf = StringBuilder::new()
i = i + 2
while i + 1 < n && !(chars[i] == '}' && chars[i + 1] == '}') {
buf.write_char(chars[i])
i = i + 1
}
tokens.push(Variable(buf.to_string().trim().to_owned()))
i = i + 2
}
'%' => {
// tag: scan until %}
flush_text(text, tokens)
let buf = StringBuilder::new()
i = i + 2
while i + 1 < n && !(chars[i] == '%' && chars[i + 1] == '}') {
buf.write_char(chars[i])
i = i + 1
}
tokens.push(Tag(buf.to_string().trim().to_owned()))
i = i + 2
}
'#' => {
// comment: skip until #}
flush_text(text, tokens)
i = i + 2
while i + 1 < n && !(chars[i] == '#' && chars[i + 1] == '}') {
i = i + 1
}
i = i + 2
}
_ => {
text.write_char(chars[i])
i = i + 1
}
}
} else {
text.write_char(chars[i])
i = i + 1
}
}
flush_text(text, tokens)
tokens
}