feature: allow opening / closing tags in middle of scripting code (#172)

This commit is contained in:
Ryan Chandler 2022-12-07 00:20:11 +00:00 committed by GitHub
parent f17fd22cdb
commit e7c83fe768
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 19 deletions

View File

@ -37,9 +37,7 @@ impl Lexer {
// The "Initial" state is used to parse inline HTML. It is essentially a catch-all
// state that will build up a single token buffer until it encounters an open tag
// of some description.
StackFrame::Initial => {
tokens.append(&mut self.initial(&mut state)?);
}
StackFrame::Initial => self.initial(&mut state, &mut tokens)?,
// The scripting state is entered when an open tag is encountered in the source code.
// This tells the lexer to start analysing characters at PHP tokens instead of inline HTML.
StackFrame::Scripting => {
@ -107,18 +105,16 @@ impl Lexer {
}
}
fn initial(&self, state: &mut State) -> SyntaxResult<Vec<Token>> {
fn initial(&self, state: &mut State, tokens: &mut Vec<Token>) -> SyntaxResult<()> {
let inline_span = state.span;
let mut buffer = Vec::new();
while let Some(char) = state.current {
if state.try_read(b"<?php") {
let tag_span = state.span;
state.skip(5);
state.replace(StackFrame::Scripting);
let mut tokens = vec![];
if !buffer.is_empty() {
tokens.push(Token {
kind: TokenKind::InlineHtml(buffer.into()),
@ -131,17 +127,19 @@ impl Lexer {
span: tag_span,
});
return Ok(tokens);
return Ok(());
}
state.next();
buffer.push(char);
}
Ok(vec![Token {
tokens.push(Token {
kind: TokenKind::InlineHtml(buffer.into()),
span: inline_span,
}])
});
Ok(())
}
fn scripting(&self, state: &mut State) -> SyntaxResult<Token> {

View File

@ -15,6 +15,11 @@ impl Parser {
let mut block = Block::new();
while !state.is_eof() && &state.current.kind != until {
if let TokenKind::OpenTag(_) = state.current.kind {
state.next();
continue;
}
block.push(self.statement(state)?);
state.skip_comments();
}

View File

@ -26,13 +26,21 @@ impl Parser {
found,
state.current.span,
));
} else {
state.next();
}
Ok(end)
}
pub(in crate::parser) fn left_brace(&self, state: &mut State) -> ParseResult<Span> {
self.skip(state, TokenKind::LeftBrace)
let span = self.skip(state, TokenKind::LeftBrace)?;
// A closing PHP tag is valid after a left brace, since
// that typically indicates the start of a block (control structures).
if state.current.kind == TokenKind::CloseTag {
state.next();
}
Ok(span)
}
pub(in crate::parser) fn right_brace(&self, state: &mut State) -> ParseResult<Span> {
@ -56,6 +64,12 @@ impl Parser {
}
pub(in crate::parser) fn colon(&self, state: &mut State) -> ParseResult<Span> {
self.skip(state, TokenKind::Colon)
let span = self.skip(state, TokenKind::Colon)?;
// A closing PHP tag is valid after a colon, since
// that typically indicates the start of a block (control structures).
if state.current.kind == TokenKind::CloseTag {
state.next();
}
Ok(span)
}
}

View File

@ -177,6 +177,11 @@ impl Parser {
state.clear_comments();
// A closing PHP tag is valid after the end of any top-level statement.
if state.current.kind == TokenKind::CloseTag {
state.next();
}
Ok(statement)
}
@ -706,13 +711,18 @@ impl Parser {
// FIXME: Tidy up duplication and make the intent a bit clearer.
match state.current.kind {
TokenKind::Colon => {
state.next();
self.colon(state)?;
let mut then = vec![];
while !matches!(
state.current.kind,
TokenKind::ElseIf | TokenKind::Else | TokenKind::EndIf
) {
if let TokenKind::OpenTag(_) = state.current.kind {
state.next();
continue;
}
then.push(self.statement(state)?);
}
@ -735,6 +745,11 @@ impl Parser {
state.current.kind,
TokenKind::ElseIf | TokenKind::Else | TokenKind::EndIf
) {
if let TokenKind::OpenTag(_) = state.current.kind {
state.next();
continue;
}
body.push(self.statement(state)?);
}
@ -746,10 +761,8 @@ impl Parser {
state.next();
self.colon(state)?;
let mut body = vec![];
while state.current.kind != TokenKind::EndIf {
body.push(self.statement(state)?);
}
let body = self.block(state, &TokenKind::EndIf)?;
r#else = Some(body);
}
@ -766,8 +779,7 @@ impl Parser {
}
_ => {
let body_end_token = if state.current.kind == TokenKind::LeftBrace {
state.next();
self.left_brace(state)?;
TokenKind::RightBrace
} else {
TokenKind::SemiColon
@ -912,6 +924,11 @@ impl Parser {
state.skip_comments();
// A closing PHP tag is valid after the end of any top-level statement.
if state.current.kind == TokenKind::CloseTag {
state.next();
}
Ok(statement)
}