use vize_l0::Vec;
use super::{Lexer, Tk};
use crate::pug::chars::parse_until;
use crate::pug::error::PugErrorCode;
use crate::pug::logical::{is_js_whitespace, skip_units};
impl Lexer<'_, '_> {
pub(super) fn text(&mut self) -> bool {
let input = self.input();
let line = input.split('\n').next().unwrap_or(input);
let start = self.pos;
let value = if let Some(rest) = line.strip_prefix('|') {
self.push(Tk::Pipe, start, start + 1);
let skip = usize::from(rest.len() >= 2 && rest.starts_with(' '));
(start + 1 + skip, start + line.len())
} else if line.starts_with(' ') {
if line.len() >= 2 {
(start + 1, start + line.len())
} else {
(start, start + 1)
}
} else {
return false;
};
self.pos = value.1;
self.add_text(Tk::Text, value.0, value.1);
true
}
pub(super) fn text_html(&mut self) -> bool {
if !self.input().starts_with('<') {
return false;
}
let (start, end) = (self.pos, self.line_end());
self.pos = end;
self.add_text(Tk::TextHtml, start, end);
true
}
pub(super) fn comment(&mut self) -> bool {
if !self.input().starts_with("//") {
return false;
}
let buffered = !crate::slice::from(self.input(), 2).starts_with('-');
let marker_end = self.pos + if buffered { 2 } else { 3 };
let end = self.line_end();
self.push(Tk::Comment, self.pos, marker_end);
self.push(Tk::CommentText, marker_end, end);
self.pos = end;
self.interpolation_allowed = buffered;
self.pipeless_text(None);
true
}
pub(super) fn pipeless_text(&mut self, indents: Option<usize>) -> bool {
let mut retry = indents;
let (lines, ptr) = 'lex: loop {
while self.blank() {}
let captures = self.scan_indentation().map(|(width, _)| width);
let indents = match (retry, captures) {
(Some(width), _) if width != 0 => width,
(_, Some(width)) => width,
_ => return false,
};
if indents <= self.top() {
return false;
}
let tabs = self.indent_re == Some(super::IndentRe::Tabs);
let indent_char = if tabs { b'\t' } else { b' ' };
let mut lines: Vec<(usize, usize, usize)> = Vec::new_in(&self.allocator);
let mut ptr = self.pos;
loop {
let line_start = ptr + 1;
let rest = crate::slice::range(self.src, line_start.min(self.end), self.end);
let line = rest.split('\n').next().unwrap_or(rest);
let line_indents = line.bytes().take_while(|&b| b == indent_char).count();
let blank = line.chars().all(is_js_whitespace);
if line_indents >= indents || blank {
let value_start = line_start + skip_units(line, indents);
lines.push((line_start, value_start, line_start + line.len()));
ptr = line_start + line.len();
} else if line_indents > self.top() {
retry = Some(line_indents);
continue 'lex;
} else {
break;
}
if ptr >= self.end {
break;
}
}
break (lines, ptr);
};
let mut lines = lines;
self.push(Tk::PipelessStart, self.pos, self.pos);
self.pos = ptr;
if self.pos >= self.end {
while lines.last().is_some_and(|&(_, from, to)| from == to) {
lines.pop();
}
}
for (index, &(line_start, from, to)) in lines.iter().enumerate() {
if index != 0 {
self.push(Tk::Newline, line_start, line_start);
}
self.add_text(Tk::Text, from, to);
}
self.push(Tk::PipelessEnd, self.pos, self.pos);
true
}
pub(super) fn add_text(&mut self, kind: Tk, mut start: usize, end: usize) {
let mut token_start = start;
let mut escapes: Vec<usize> = Vec::new_in(&self.allocator);
let mut has_prefix = false;
loop {
let value = crate::slice::range(self.src, start, end);
let none = usize::MAX;
let at_end = if self.interpolated {
value.find(']')
} else {
None
};
let at_end = at_end.unwrap_or(none);
let allowed = self.interpolation_allowed;
let at_tag = allowed.then(|| value.find("#[")).flatten().unwrap_or(none);
let at_escape = allowed
.then(|| value.find("\\#["))
.flatten()
.unwrap_or(none);
let code = allowed.then(|| find_code_interpolation(value)).flatten();
let at_code = code.map_or(none, |(at, _)| at);
if at_escape < at_end && at_escape < at_tag && at_escape < at_code {
escapes.push(start + at_escape);
start += at_escape + 3;
has_prefix = true;
continue;
}
if at_tag < at_end && at_tag < at_escape && at_tag < at_code {
self.emit_text(kind, token_start, start + at_tag, &escapes);
let open = start + at_tag;
self.push(Tk::InterpOpen, open, open + 2);
let resume = self.child(open + 2, end);
start = resume;
token_start = resume;
escapes.clear();
has_prefix = false;
continue;
}
if at_end < at_tag && at_end < at_escape && at_end < at_code {
if has_prefix || at_end > 0 {
self.emit_text(kind, token_start, start + at_end, &escapes);
}
self.push(Tk::InterpClose, start + at_end, start + at_end + 1);
self.pos = start + at_end + 1;
self.ended = true;
return;
}
if let Some((at, escaped)) = code {
if escaped {
escapes.push(start + at);
start += at + 3;
has_prefix = true;
continue;
}
if has_prefix || at > 0 {
self.emit_text(kind, token_start, start + at, &escapes);
}
let body = start + at + 2;
match parse_until(
self.allocator,
crate::slice::range(self.src, body, end),
b'}',
0,
) {
Ok(close) => {
self.push(Tk::CodeInterp, start + at, body + close + 1);
if body + close + 1 < end {
start = body + close + 1;
token_start = start;
escapes.clear();
has_prefix = false;
continue;
}
}
Err(_) => {
self.error(PugErrorCode::NoEndBracket, start + at);
self.push(Tk::CodeInterp, start + at, end);
}
}
return;
}
self.emit_text(kind, token_start, end, &escapes);
return;
}
}
fn emit_text(&mut self, kind: Tk, start: usize, end: usize, escapes: &[usize]) {
let mut from = start;
for &escape in escapes {
self.push(kind, from, escape);
self.push(Tk::Escape, escape, escape + 1);
from = escape + 1;
}
self.push(kind, from, end);
}
fn child(&mut self, start: usize, end: usize) -> usize {
let mut child = Lexer::new(
self.allocator,
self.src,
start,
end,
self.out,
self.errors,
true,
);
vize_l0::ensure_sufficient_stack(|| child.run());
child.pos
}
}
fn find_code_interpolation(value: &str) -> Option<(usize, bool)> {
let bytes = value.as_bytes();
bytes.iter().enumerate().find_map(|(at, &byte)| {
let opens = |from: usize| {
matches!(bytes.get(from), Some(b'#' | b'!')) && bytes.get(from + 1) == Some(&b'{')
};
if byte == b'\\' && opens(at + 1) {
Some((at, true))
} else if opens(at) {
Some((at, false))
} else {
None
}
})
}