mod tag;
mod text;
use vize_l0::{Allocator, Box, Vec};
use super::error::{PugError, PugErrorCode};
use super::lex::{Tk, Tok};
use super::logical::Logical;
use super::tree::{PugCode, PugComment, PugNode, PugRefusal, PugRefused, PugTree, PugUnexpected};
use crate::markup::surface::Token;
pub(crate) struct Builder<'a, 't> {
allocator: &'a Allocator,
source: &'a str,
logical: &'t Logical<'a>,
tokens: &'t [Tok],
at: usize,
cursor: usize,
errors: &'t mut Vec<'a, PugError>,
in_mixin: u32,
}
pub(crate) fn build<'a>(
allocator: &'a Allocator,
source: &'a str,
logical: &Logical<'a>,
tokens: &[Tok],
errors: &mut Vec<'a, PugError>,
) -> PugTree<'a> {
let mut builder = Builder {
allocator,
source,
logical,
tokens,
at: 0,
cursor: 0,
errors,
in_mixin: 0,
};
let mut nodes = Vec::new_in(&allocator);
loop {
match builder.peek() {
Tk::Eos => break,
Tk::Newline | Tk::Outdent => builder.skip(),
Tk::Dot => nodes.push(builder.parse_dot(true)),
_ => builder.push_expr(&mut nodes),
}
}
let eof = Token::present(
crate::slice::from(source, builder.cursor),
crate::slice::from(source, source.len()),
);
PugTree { source, nodes, eof }
}
impl<'a> Builder<'a, '_> {
fn peek(&mut self) -> Tk {
loop {
let Some(token) = self.tokens.get(self.at) else {
return Tk::Eos;
};
let unbuffered = token.kind == Tk::Comment && token.end - token.start == 3;
if !unbuffered {
return token.kind;
}
self.at += 1;
if self
.tokens
.get(self.at)
.is_some_and(|t| t.kind == Tk::CommentText)
{
self.at += 1;
}
if self
.tokens
.get(self.at)
.is_some_and(|t| t.kind == Tk::PipelessStart)
{
while self
.tokens
.get(self.at)
.is_some_and(|t| t.kind != Tk::PipelessEnd)
{
self.at += 1;
}
self.at += 1;
}
}
}
fn peek_is(&mut self, kind: Tk) -> bool {
self.peek() == kind
}
fn skip(&mut self) {
self.peek();
self.at += 1;
}
fn take(&mut self) -> Token<'a> {
self.peek();
let Some(&tok) = self.tokens.get(self.at) else {
return self.hole();
};
self.at += 1;
let (start, end) = self.logical.range(tok.start as usize, tok.end as usize);
let start = (start as usize).max(self.cursor);
let end = (end as usize).max(start);
debug_assert!(start >= self.cursor, "pug tokens are in source order");
let token = Token::present(
crate::slice::range(self.source, self.cursor, start),
crate::slice::range(self.source, start, end),
);
self.cursor = end;
token
}
fn take_missing(&mut self) -> Token<'a> {
self.peek();
let Some(&tok) = self.tokens.get(self.at) else {
return self.hole();
};
self.at += 1;
let at = (self.logical.at(tok.start as usize) as usize).max(self.cursor);
let token = Token::missing(
crate::slice::range(self.source, self.cursor, at),
crate::slice::range(self.source, at, at),
);
self.cursor = at;
token
}
fn hole(&self) -> Token<'a> {
let here = crate::slice::range(self.source, self.cursor, self.cursor);
Token::missing(here, here)
}
fn offset(&mut self) -> u32 {
self.peek();
self.tokens
.get(self.at)
.map_or(self.source.len() as u32, |tok| {
self.logical.at(tok.start as usize)
})
}
fn error(&mut self, code: PugErrorCode) {
let offset = self.offset();
self.errors.push(PugError { code, offset });
}
fn boxed<T>(&self, value: T) -> Box<'a, T> {
Box::new_in(value, &self.allocator)
}
fn nodes(&self) -> Vec<'a, PugNode<'a>> {
Vec::new_in(&self.allocator)
}
fn push_expr(&mut self, nodes: &mut Vec<'a, PugNode<'a>>) {
let before = self.at;
let node = vize_l0::ensure_sufficient_stack(|| self.parse_expr());
nodes.push(node);
if self.at == before {
let token = self.take();
nodes.push(self.unexpected(token));
}
}
fn unexpected(&self, token: Token<'a>) -> PugNode<'a> {
let block = self.nodes();
PugNode::Unexpected(self.boxed(PugUnexpected { token, block }))
}
fn block(&mut self) -> Vec<'a, PugNode<'a>> {
self.skip();
let mut nodes = self.nodes();
loop {
match self.peek() {
Tk::Outdent => {
self.skip();
break;
}
Tk::Eos => break,
Tk::Newline => self.skip(),
_ => self.push_expr(&mut nodes),
}
}
nodes
}
fn parse_expr(&mut self) -> PugNode<'a> {
match self.peek() {
Tk::Tag | Tk::Class | Tk::Id => self.parse_tag(),
Tk::Call => self.parse_call(),
Tk::Keyword(refusal) => {
if refusal == PugRefusal::MixinBlock && self.in_mixin == 0 {
self.error(PugErrorCode::BlockOutsideMixin);
}
let head = self.take();
let body = self.peek_is(Tk::PipelessStart).then(|| self.text_block());
let mixin = u32::from(refusal == PugRefusal::Mixin);
self.in_mixin += mixin;
let block = if self.peek_is(Tk::Indent) {
self.block()
} else {
self.nodes()
};
self.in_mixin -= mixin;
PugNode::Refused(self.boxed(PugRefused {
refusal,
head,
body,
block,
}))
}
Tk::Comment => {
let marker = self.take();
let text = self.take();
let body = self.peek_is(Tk::PipelessStart).then(|| self.text_block());
PugNode::Comment(self.boxed(PugComment { marker, text, body }))
}
Tk::Pipe | Tk::Text | Tk::Escape | Tk::CodeInterp | Tk::InterpOpen => {
let text = self.text_run(true);
PugNode::Text(self.boxed(text))
}
Tk::TextHtml => self.parse_html(),
Tk::Dot => self.parse_dot(false),
Tk::CodeFlag => {
let code = self.parse_code(false);
PugNode::Code(self.boxed(code))
}
Tk::Indent => {
self.error(PugErrorCode::InvalidToken);
let at = self.offset() as usize;
let token = Token::present(
crate::slice::range(self.source, self.cursor, at),
crate::slice::range(self.source, at, at),
);
self.cursor = at;
let block = self.block();
PugNode::Unexpected(self.boxed(PugUnexpected { token, block }))
}
Tk::Newline | Tk::Outdent | Tk::Eos | Tk::InterpClose | Tk::InterpCloseMissing => {
self.error(PugErrorCode::InvalidToken);
let at = self.offset() as usize;
let token = Token::present(
crate::slice::range(self.source, self.cursor, at),
crate::slice::range(self.source, at, at),
);
self.cursor = at;
self.unexpected(token)
}
Tk::Unexpected => {
let token = self.take();
self.unexpected(token)
}
_ => {
self.error(PugErrorCode::InvalidToken);
let token = self.take();
self.unexpected(token)
}
}
}
fn parse_code(&mut self, inline: bool) -> PugCode<'a> {
let flag = self.take();
let code = if self.peek_is(Tk::Code) {
self.take()
} else {
self.hole()
};
let block = if !inline && self.peek_is(Tk::Indent) {
self.block()
} else {
self.nodes()
};
PugCode { flag, code, block }
}
}