use oak_core::{
parser::{ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer},
source::{Source, TextEdit},
};
pub mod element_type;
use crate::{
language::JinjaLanguage,
lexer::{JinjaLexer, token_type::JinjaTokenType},
};
use element_type::JinjaElementType;
pub(crate) type State<'a, S> = ParserState<'a, JinjaLanguage, S>;
#[derive(Debug, Clone)]
pub struct JinjaParser<'config> {
config: &'config JinjaLanguage,
}
impl<'config> JinjaParser<'config> {
pub fn new(config: &'config JinjaLanguage) -> Self {
Self { config }
}
fn parse_node<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
match state.peek_kind() {
Some(JinjaTokenType::DoubleLeftBrace) => self.parse_variable(state),
Some(JinjaTokenType::LeftBracePercent) => self.parse_tag_statement(state),
Some(JinjaTokenType::Comment) => {
let cp = state.checkpoint();
state.bump();
state.finish_at(cp, JinjaElementType::Comment);
Ok(())
}
_ => {
let cp = state.checkpoint();
state.advance();
state.finish_at(cp, JinjaElementType::Text);
Ok(())
}
}
}
fn parse_variable<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
let cp = state.checkpoint();
state.expect(JinjaTokenType::DoubleLeftBrace)?;
self.parse_expression(state)?;
state.expect(JinjaTokenType::DoubleRightBrace)?;
state.finish_at(cp, JinjaElementType::Variable);
Ok(())
}
fn parse_tag_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
let cp = state.checkpoint();
state.expect(JinjaTokenType::LeftBracePercent)?;
let kind = state.peek_kind();
match kind {
Some(JinjaTokenType::Identifier) => {
let text = state.peek_text().unwrap_or_default();
match text.as_ref() {
"if" => self.parse_if_statement(state, cp),
"for" => self.parse_for_statement(state, cp),
"block" => self.parse_block_statement(state, cp),
"macro" => self.parse_macro_definition(state, cp),
_ => {
while state.not_at_end() && !state.at(JinjaTokenType::PercentRightBrace) {
state.advance();
}
state.expect(JinjaTokenType::PercentRightBrace)?;
state.finish_at(cp, JinjaElementType::Tag);
Ok(())
}
}
}
_ => {
while state.not_at_end() && !state.at(JinjaTokenType::PercentRightBrace) {
state.advance();
}
state.expect(JinjaTokenType::PercentRightBrace)?;
state.finish_at(cp, JinjaElementType::Tag);
Ok(())
}
}
}
fn parse_if_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
state.expect(JinjaTokenType::Identifier)?; self.parse_expression(state)?;
state.expect(JinjaTokenType::PercentRightBrace)?;
while state.not_at_end() {
if state.at(JinjaTokenType::LeftBracePercent) {
if let Some(JinjaTokenType::Identifier) = state.peek_kind_at(1) {
let text = state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).unwrap_or_default();
if text == "endif" || text == "elif" || text == "else" {
break;
}
}
}
self.parse_node(state)?;
}
if state.at(JinjaTokenType::LeftBracePercent) {
let text = state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).unwrap_or_default();
if text == "elif" {
state.expect(JinjaTokenType::LeftBracePercent)?;
self.parse_if_statement(state, state.checkpoint())?;
}
else if text == "else" {
state.expect(JinjaTokenType::LeftBracePercent)?;
state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::PercentRightBrace)?;
while state.not_at_end() && !(state.at(JinjaTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endif").unwrap_or(false)) {
self.parse_node(state)?;
}
}
}
if state.at(JinjaTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endif").unwrap_or(false) {
state.expect(JinjaTokenType::LeftBracePercent)?;
state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::PercentRightBrace)?;
}
state.finish_at(cp, JinjaElementType::IfStatement);
Ok(())
}
fn parse_for_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
state.expect(JinjaTokenType::Identifier)?; self.parse_expression(state)?; if state.peek_text().map(|t| t == "in").unwrap_or(false) {
state.advance();
self.parse_expression(state)?; }
state.expect(JinjaTokenType::PercentRightBrace)?;
while state.not_at_end() && !(state.at(JinjaTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endfor").unwrap_or(false)) {
self.parse_node(state)?;
}
state.expect(JinjaTokenType::LeftBracePercent)?;
state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::PercentRightBrace)?;
state.finish_at(cp, JinjaElementType::ForStatement);
Ok(())
}
fn parse_block_statement<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::PercentRightBrace)?;
while state.not_at_end() && !(state.at(JinjaTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endblock").unwrap_or(false)) {
self.parse_node(state)?;
}
state.expect(JinjaTokenType::LeftBracePercent)?;
state.expect(JinjaTokenType::Identifier)?; if state.at(JinjaTokenType::Identifier) {
state.advance();
}
state.expect(JinjaTokenType::PercentRightBrace)?;
state.finish_at(cp, JinjaElementType::Block);
Ok(())
}
fn parse_macro_definition<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, cp: (usize, usize)) -> Result<(), oak_core::OakError> {
state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::Identifier)?;
if state.at(JinjaTokenType::LeftParen) {
state.advance();
while state.not_at_end() && !state.at(JinjaTokenType::RightParen) {
if state.at(JinjaTokenType::Identifier) {
state.advance();
}
if state.at(JinjaTokenType::Comma) {
state.advance();
}
}
state.expect(JinjaTokenType::RightParen)?;
}
state.expect(JinjaTokenType::PercentRightBrace)?;
while state.not_at_end() && !(state.at(JinjaTokenType::LeftBracePercent) && state.tokens.peek_at(1).map(|t| state.source.get_text_in(t.span)).map(|t| t == "endmacro").unwrap_or(false)) {
self.parse_node(state)?;
}
state.expect(JinjaTokenType::LeftBracePercent)?;
state.expect(JinjaTokenType::Identifier)?; state.expect(JinjaTokenType::PercentRightBrace)?;
state.finish_at(cp, JinjaElementType::MacroDefinition);
Ok(())
}
fn parse_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
self.parse_binary_expression(state, 0)
}
fn parse_binary_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, min_precedence: i32) -> Result<(), oak_core::OakError> {
let cp = state.checkpoint();
self.parse_primary_expression(state)?;
while let Some(kind) = state.peek_kind() {
let precedence = self.get_precedence(kind);
if precedence < min_precedence {
break;
}
state.advance();
self.parse_binary_expression(state, precedence + 1)?;
state.finish_at(cp, if kind == JinjaTokenType::Pipe { JinjaElementType::Filter } else { JinjaElementType::Expression });
}
Ok(())
}
fn get_precedence(&self, kind: JinjaTokenType) -> i32 {
match kind {
JinjaTokenType::Pipe => 1,
JinjaTokenType::Plus | JinjaTokenType::Minus => 2,
JinjaTokenType::Star | JinjaTokenType::Slash => 3,
_ => -1,
}
}
fn parse_primary_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> Result<(), oak_core::OakError> {
let cp = state.checkpoint();
match state.peek_kind() {
Some(JinjaTokenType::Identifier) => {
state.advance();
if state.at(JinjaTokenType::LeftParen) {
state.advance();
while state.not_at_end() && !state.at(JinjaTokenType::RightParen) {
self.parse_expression(state)?;
if state.at(JinjaTokenType::Comma) {
state.advance();
}
}
state.expect(JinjaTokenType::RightParen)?;
state.finish_at(cp, JinjaElementType::Function);
}
else {
state.finish_at(cp, JinjaElementType::Identifier);
}
}
Some(JinjaTokenType::String) | Some(JinjaTokenType::Number) | Some(JinjaTokenType::Boolean) => {
state.advance();
state.finish_at(cp, JinjaElementType::Literal);
}
Some(JinjaTokenType::LeftParen) => {
state.advance();
self.parse_expression(state)?;
state.expect(JinjaTokenType::RightParen)?;
}
_ => {
while state.not_at_end() && !state.at(JinjaTokenType::PercentRightBrace) && !state.at(JinjaTokenType::DoubleRightBrace) && !state.at(JinjaTokenType::RightParen) && !state.at(JinjaTokenType::Comma) {
state.advance();
}
}
}
Ok(())
}
}
impl<'config> Parser<JinjaLanguage> for JinjaParser<'config> {
fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<JinjaLanguage>) -> ParseOutput<'a, JinjaLanguage> {
let lexer = JinjaLexer::new(&self.config);
parse_with_lexer(&lexer, text, edits, cache, |state| {
let checkpoint = state.checkpoint();
while state.not_at_end() {
self.parse_node(state)?;
}
Ok(state.finish_at(checkpoint, JinjaElementType::Root))
})
}
}