pub mod element_type;
use crate::{
language::VueLanguage,
lexer::{VueLexer, token_type::VueTokenType},
};
use oak_core::{
GreenNode, TextEdit,
parser::{
ParseCache, ParseOutput, Parser, ParserState, parse_with_lexer,
pratt::{Associativity, Pratt, PrattParser, unary},
},
source::{Source, SourceText},
};
pub(crate) type State<'a, S> = ParserState<'a, VueLanguage, S>;
pub struct VueParser<'config> {
pub(crate) config: &'config VueLanguage,
}
impl<'config> Parser<VueLanguage> for VueParser<'config> {
fn parse<'a, S: Source + ?Sized>(&self, text: &'a S, edits: &[TextEdit], cache: &'a mut impl ParseCache<VueLanguage>) -> ParseOutput<'a, VueLanguage> {
let lexer = VueLexer::new(self.config);
parse_with_lexer(&lexer, text, edits, cache, |state| {
let cp = state.checkpoint();
while state.not_at_end() {
if state.at(VueTokenType::TemplateStart) {
self.parse_special_block(state, VueTokenType::TemplateStart, "template", true);
}
else if state.at(VueTokenType::ScriptStart) {
self.parse_special_block(state, VueTokenType::ScriptStart, "script", false);
}
else if state.at(VueTokenType::StyleStart) {
self.parse_special_block(state, VueTokenType::StyleStart, "style", false);
}
else {
self.parse_node(state);
}
}
Ok(state.finish_at(cp, crate::parser::element_type::VueElementType::Root))
})
}
}
#[allow(dead_code)]
impl<'config> VueParser<'config> {
pub fn new(config: &'config VueLanguage) -> Self {
Self { config }
}
pub fn parse_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> &'a GreenNode<'a, VueLanguage> {
PrattParser::parse(state, 0, self)
}
pub fn parse_expression_only<'a, S: Source + ?Sized>(&self, source: &'a S, session: &'a mut oak_core::parser::ParseSession<VueLanguage>) -> oak_core::parser::ParseOutput<'a, VueLanguage> {
let lexer = VueLexer::new(self.config);
oak_core::parser::parse_with_lexer(&lexer, source, &[], session, |state| {
let cp = state.checkpoint();
self.parse_expression(state);
Ok(state.finish_at(cp, crate::parser::element_type::VueElementType::Root))
})
}
pub fn parse_v_for_only<'a, S: Source + ?Sized>(&self, source: &'a S, session: &'a mut oak_core::parser::ParseSession<VueLanguage>) -> oak_core::parser::ParseOutput<'a, VueLanguage> {
let lexer = VueLexer::new(self.config);
oak_core::parser::parse_with_lexer(&lexer, source, &[], session, |state| {
let cp = state.checkpoint();
self.parse_v_for_expression(state);
Ok(state.finish_at(cp, crate::parser::element_type::VueElementType::Root))
})
}
fn parse_special_block<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, start_kind: VueTokenType, tag_name: &str, recursive: bool) {
let cp = state.checkpoint();
let tag_cp = state.checkpoint();
state.expect(start_kind).ok();
while state.not_at_end() && !state.at(VueTokenType::Gt) {
if state.at(VueTokenType::Whitespace) {
state.bump();
continue;
}
self.parse_attribute(state);
}
state.expect(VueTokenType::Gt).ok();
let tag_node = state.finish_at(tag_cp, crate::parser::element_type::VueElementType::Tag);
state.sink.restore(tag_cp.1);
state.push_child(tag_node);
if tag_name == "script" {
self.parse_program(state);
}
else if recursive {
while state.not_at_end() && !state.at(VueTokenType::LtSlash) {
self.parse_node(state);
}
}
else {
while state.not_at_end() && !state.at(VueTokenType::LtSlash) {
state.bump();
}
}
if state.at(VueTokenType::LtSlash) {
let close_cp = state.checkpoint();
state.expect(VueTokenType::LtSlash).ok();
state.expect(VueTokenType::Identifier).ok();
state.expect(VueTokenType::Gt).ok();
let close_node = state.finish_at(close_cp, crate::parser::element_type::VueElementType::CloseTag);
state.sink.restore(close_cp.1);
state.push_child(close_node);
}
let node = state.finish_at(cp, crate::parser::element_type::VueElementType::Element);
state.sink.restore(cp.1);
state.push_child(node);
}
fn parse_node<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
if state.at(VueTokenType::Lt) || state.at(VueTokenType::ScriptStart) || state.at(VueTokenType::StyleStart) || state.at(VueTokenType::TemplateStart) || state.at(VueTokenType::DocTypeStart) {
self.parse_element(state);
}
else if state.at(VueTokenType::InterpolationStart) {
self.parse_interpolation(state);
}
else if state.at(VueTokenType::Comment) {
let cp = state.checkpoint();
state.bump();
let node = state.finish_at(cp, crate::parser::element_type::VueElementType::CommentNode);
state.sink.restore(cp.1);
state.push_child(node);
}
else if state.at(VueTokenType::LtSlash) {
return;
}
else {
let cp = state.checkpoint();
while state.not_at_end()
&& !state.at(VueTokenType::Lt)
&& !state.at(VueTokenType::LtSlash)
&& !state.at(VueTokenType::ScriptStart)
&& !state.at(VueTokenType::StyleStart)
&& !state.at(VueTokenType::TemplateStart)
&& !state.at(VueTokenType::DocTypeStart)
&& !state.at(VueTokenType::InterpolationStart)
{
state.bump();
}
let node = state.finish_at(cp, crate::parser::element_type::VueElementType::TextNode);
state.sink.restore(cp.1);
state.push_child(node);
}
}
fn parse_element<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
let cp = state.checkpoint();
let tag_cp = state.checkpoint();
let mut tag_name = String::new();
let mut _start_kind = VueTokenType::Lt;
if state.at(VueTokenType::Lt) {
state.expect(VueTokenType::Lt).ok();
if state.at(VueTokenType::Identifier) {
tag_name = state.peek_text().unwrap_or_default().to_lowercase();
state.bump();
}
}
else if state.at(VueTokenType::ScriptStart) {
_start_kind = VueTokenType::ScriptStart;
state.bump();
tag_name = "script".to_string();
}
else if state.at(VueTokenType::StyleStart) {
_start_kind = VueTokenType::StyleStart;
state.bump();
tag_name = "style".to_string();
}
else if state.at(VueTokenType::TemplateStart) {
_start_kind = VueTokenType::TemplateStart;
state.bump();
tag_name = "template".to_string();
}
else if state.at(VueTokenType::DocTypeStart) {
state.bump();
while state.not_at_end() && !state.at(VueTokenType::Gt) {
state.bump();
}
state.expect(VueTokenType::Gt).ok();
let node = state.finish_at(cp, crate::parser::element_type::VueElementType::Element);
state.sink.restore(cp.1);
state.push_child(node);
return;
}
if tag_name == "script" || tag_name == "style" || tag_name == "template" {
state.sink.restore(cp.1); }
while state.not_at_end() && !state.at(VueTokenType::Gt) && !state.at(VueTokenType::SlashGt) {
if state.at(VueTokenType::Whitespace) {
state.bump();
continue;
}
self.parse_attribute(state);
}
let mut is_self_closing = false;
if state.at(VueTokenType::SlashGt) {
state.bump();
is_self_closing = true;
}
else {
state.expect(VueTokenType::Gt).ok();
}
let tag_node = state.finish_at(tag_cp, crate::parser::element_type::VueElementType::Tag);
state.sink.restore(tag_cp.1);
state.push_child(tag_node);
if !is_self_closing && tag_name != "img" && tag_name != "br" && tag_name != "hr" && tag_name != "input" && tag_name != "meta" && tag_name != "link" {
while state.not_at_end() && !state.at(VueTokenType::LtSlash) {
self.parse_node(state);
}
if state.at(VueTokenType::LtSlash) {
let close_cp = state.checkpoint();
state.expect(VueTokenType::LtSlash).ok();
if state.at(VueTokenType::Identifier) {
state.bump();
}
state.expect(VueTokenType::Gt).ok();
let close_node = state.finish_at(close_cp, crate::parser::element_type::VueElementType::CloseTag);
state.sink.restore(close_cp.1);
state.push_child(close_node);
}
}
let node = state.finish_at(cp, crate::parser::element_type::VueElementType::Element);
state.sink.restore(cp.1);
state.push_child(node);
}
fn parse_attribute<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
let cp = state.checkpoint();
let mut is_directive = false;
let mut directive_name = String::new();
if state.at(VueTokenType::Colon) {
state.bump();
is_directive = true;
directive_name = "v-bind".to_string();
}
else if state.at(VueTokenType::At) {
state.bump();
is_directive = true;
directive_name = "v-on".to_string();
}
else if state.at(VueTokenType::Hash) {
state.bump();
is_directive = true;
directive_name = "v-slot".to_string();
}
let name_cp = state.checkpoint();
if state.at(VueTokenType::Identifier) {
let text = state.peek_text().map(|c| c.to_string()).unwrap_or_default();
if text.starts_with("v-") {
is_directive = true;
directive_name = text;
}
state.bump();
}
if state.at(VueTokenType::Colon) {
state.bump();
state.expect(VueTokenType::Identifier).ok();
}
while state.at(VueTokenType::Dot) {
let mod_cp = state.checkpoint();
state.bump();
state.expect(VueTokenType::Identifier).ok();
state.finish_at(mod_cp, crate::parser::element_type::VueElementType::Modifier);
}
state.finish_at(name_cp, crate::parser::element_type::VueElementType::AttributeName);
if state.eat(VueTokenType::Eq) {
let val_cp = state.checkpoint();
if is_directive {
self.parse_directive_value(state, &directive_name);
}
else if state.at(VueTokenType::StringLiteral) {
state.bump();
}
else {
self.parse_expression(state);
}
state.finish_at(val_cp, crate::parser::element_type::VueElementType::AttributeValue);
}
let kind = if is_directive { crate::parser::element_type::VueElementType::Directive } else { crate::parser::element_type::VueElementType::Attribute };
state.finish_at(cp, kind);
}
fn parse_directive_value<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, directive_name: &str) {
if state.at(VueTokenType::StringLiteral) {
if let Some(text) = state.peek_text() {
if text.len() >= 2 {
let inner_text = &text[1..text.len() - 1];
let _inner_source = SourceText::new(inner_text.to_string());
let _lexer = VueLexer::new(self.config);
let mut _temp_cache: oak_core::parser::ParseSession<VueLanguage> = oak_core::parser::ParseSession::default();
let mut inner_state = state.nested();
if directive_name == "v-for" {
self.parse_v_for_expression(&mut inner_state);
}
else {
self.parse_expression(&mut inner_state);
}
}
}
state.bump();
}
}
pub fn parse_v_for_expression<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
let cp = state.checkpoint();
self.skip_whitespace(state);
let pattern_cp = state.checkpoint();
if state.at(VueTokenType::LeftParen) {
state.bump();
self.skip_whitespace(state);
state.expect(VueTokenType::Identifier).ok();
self.skip_whitespace(state);
if state.eat(VueTokenType::Comma) {
self.skip_whitespace(state);
state.expect(VueTokenType::Identifier).ok();
self.skip_whitespace(state);
}
state.expect(VueTokenType::RightParen).ok();
}
else {
state.expect(VueTokenType::Identifier).ok();
}
state.finish_at(pattern_cp, crate::parser::element_type::VueElementType::Pattern);
self.skip_whitespace(state);
let kind = if state.at(VueTokenType::In) {
state.bump();
VueTokenType::ForInExpr
}
else if state.at(VueTokenType::Of) {
state.bump();
VueTokenType::ForOfExpr
}
else {
state.finish_at(cp, crate::parser::element_type::VueElementType::Error);
return;
};
self.skip_whitespace(state);
let expr = self.parse_expression(state);
state.push_child(expr);
let element_kind = match kind {
VueTokenType::ForInExpr => crate::parser::element_type::VueElementType::ForInExpr,
VueTokenType::ForOfExpr => crate::parser::element_type::VueElementType::ForOfExpr,
_ => crate::parser::element_type::VueElementType::Error,
};
state.finish_at(cp, element_kind);
}
fn parse_interpolation<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
let cp = state.checkpoint();
state.expect(VueTokenType::InterpolationStart).ok();
self.skip_whitespace(state);
let expr = self.parse_expression(state);
state.push_child(expr);
self.skip_whitespace(state);
state.expect(VueTokenType::InterpolationEnd).ok();
state.finish_at(cp, crate::parser::element_type::VueElementType::Interpolation);
}
fn skip_whitespace<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) {
while state.at(VueTokenType::Whitespace) {
state.bump();
}
}
fn parse_program<'a, S: Source + ?Sized>(&self, _state: &mut State<'a, S>) {
}
}
impl<'config> Pratt<VueLanguage> for VueParser<'config> {
fn primary<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> &'a GreenNode<'a, VueLanguage> {
use VueTokenType::*;
let cp = state.checkpoint();
self.skip_whitespace(state);
let node = if state.at(LeftParen) {
state.expect(LeftParen).ok();
let inner = PrattParser::parse(state, 0, self);
state.push_child(inner);
state.expect(RightParen).ok();
state.finish_at(cp, crate::parser::element_type::VueElementType::Expression)
}
else if state.at(Identifier) {
state.expect(Identifier).ok();
state.finish_at(cp, crate::parser::element_type::VueElementType::Identifier)
}
else if state.at(StringLiteral) {
state.expect(StringLiteral).ok();
state.finish_at(cp, crate::parser::element_type::VueElementType::Literal)
}
else if state.at(NumberLiteral) {
state.expect(NumberLiteral).ok();
state.finish_at(cp, crate::parser::element_type::VueElementType::Literal)
}
else {
state.bump();
state.finish_at(cp, crate::parser::element_type::VueElementType::Error)
};
state.sink.restore(cp.1);
node
}
fn prefix<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>) -> &'a GreenNode<'a, VueLanguage> {
use VueTokenType::*;
let start_cp = state.checkpoint();
self.skip_whitespace(state);
let kind = match state.peek_kind() {
Some(k) => k,
None => {
state.restore(start_cp);
return self.primary(state);
}
};
match kind {
Bang | Minus => {
state.restore(start_cp);
let cp = state.checkpoint();
self.skip_whitespace(state);
let node = unary(state, kind.into(), 90, UnaryExpr.into(), |s, p| PrattParser::parse(s, p, self));
state.sink.restore(cp.1);
node
}
_ => {
state.restore(start_cp);
self.primary(state)
}
}
}
fn infix<'a, S: Source + ?Sized>(&self, state: &mut State<'a, S>, left: &'a GreenNode<'a, VueLanguage>, min_precedence: u8) -> Option<&'a GreenNode<'a, VueLanguage>> {
use VueTokenType::*;
let start_cp = state.checkpoint();
self.skip_whitespace(state);
let kind = match state.peek_kind() {
Some(k) => k,
None => {
state.restore(start_cp);
return None;
}
};
let (prec, assoc) = match kind {
Dot | LeftBracket | LeftParen => (100, Associativity::Left),
Star | Slash => (80, Associativity::Left),
Plus | Minus => (70, Associativity::Left),
Lt | Gt => (60, Associativity::Left),
EqEq => (50, Associativity::Left),
And => (40, Associativity::Left),
Or => (30, Associativity::Left),
Eq => (10, Associativity::Right),
_ => {
state.restore(start_cp);
return None;
}
};
if prec < min_precedence {
state.restore(start_cp);
return None;
}
state.restore(start_cp);
let op_cp = state.checkpoint();
state.push_child(left);
self.skip_whitespace(state);
let node = match kind {
Dot => {
state.expect(Dot).ok();
self.skip_whitespace(state);
state.expect(Identifier).ok();
state.finish_at(op_cp, crate::parser::element_type::VueElementType::MemberExpr)
}
LeftBracket => {
state.expect(LeftBracket).ok();
self.skip_whitespace(state);
let inner = PrattParser::parse(state, 0, self);
state.push_child(inner);
self.skip_whitespace(state);
state.expect(RightBracket).ok();
state.finish_at(op_cp, crate::parser::element_type::VueElementType::MemberExpr)
}
LeftParen => {
state.expect(LeftParen).ok();
self.skip_whitespace(state);
if !state.at(RightParen) {
loop {
let arg = PrattParser::parse(state, 0, self);
state.push_child(arg);
self.skip_whitespace(state);
if state.expect(Comma).is_ok() {
self.skip_whitespace(state);
}
else {
break;
}
}
}
state.expect(RightParen).ok();
state.finish_at(op_cp, crate::parser::element_type::VueElementType::CallExpr)
}
_ => {
state.expect(kind).ok();
let next_prec = match assoc {
Associativity::Left => prec + 1,
Associativity::Right => prec,
Associativity::None => prec + 1,
};
let right = {
self.skip_whitespace(state);
PrattParser::parse(state, next_prec, self)
};
state.push_child(right);
state.finish_at(op_cp, crate::parser::element_type::VueElementType::BinaryExpr)
}
};
state.sink.restore(op_cp.1);
state.sink.restore(op_cp.1);
Some(node)
}
}