use super::punctuated::{Pair, Punctuated};
use crate::{
node::Node,
tokenizer::TokenReference,
visitors::{Visit, VisitMut},
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt};
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ParserState<'a> {
pub index: usize,
pub len: usize,
pub tokens: &'a [TokenReference],
}
impl<'a> ParserState<'a> {
pub fn new(tokens: &'a [TokenReference]) -> ParserState<'a> {
ParserState {
index: 0,
len: tokens.len(),
tokens,
}
}
pub fn advance(self) -> Option<ParserState<'a>> {
if self.index + 1 == self.len {
None
} else {
Some(ParserState {
index: self.index + 1,
..self
})
}
}
pub fn peek(&self) -> &TokenReference {
assert!(
self.index < self.len,
"peek failed, when there should always be an eof"
);
self.tokens.get(self.index).expect("couldn't peek, no eof?")
}
}
impl<'a> fmt::Debug for ParserState<'a> {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"ParserState {{ index: {}, current: {:?} }}",
self.index,
self.peek()
)
}
}
pub(crate) trait Parser: Sized {
type Item;
#[allow(clippy::result_large_err)]
fn parse<'a>(
&self,
state: ParserState<'a>,
) -> Result<(ParserState<'a>, Self::Item), InternalAstError>;
}
#[doc(hidden)]
#[macro_export]
macro_rules! make_op {
($enum:ident, $(#[$outer:meta])* { $($(#[$inner:meta])* $operator:ident,)+ }) => {
#[derive(Clone, Debug, Display, PartialEq, Eq, Node, Visit)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
#[non_exhaustive]
$(#[$outer])*
#[display(fmt = "{}")]
pub enum $enum {
$(
$(#[$inner])*
#[allow(missing_docs)]
$operator(TokenReference),
)+
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! define_parser {
($parser:ty, $node:ty, |_, $state:ident| $body:expr) => {
define_parser! {$parser, $node, |_, mut $state: ParserState<'a>| $body}
};
($parser:ty, $node:ty, |$self:ident, $state:ident| $body:expr) => {
define_parser! {$parser, $node, |$self:&$parser, mut $state: ParserState<'a>| $body}
};
($parser:ty, $node:ty, $body:expr) => {
impl Parser for $parser {
type Item = $node;
#[allow(unused_mut)]
fn parse<'a>(
&self,
state: ParserState<'a>,
) -> Result<(ParserState<'a>, $node), InternalAstError> {
#[cfg(feature = "stacker")]
if true {
return stacker::maybe_grow(32 * 1024, 1024 * 1024, || $body(self, state));
}
$body(self, state)
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! parse_first_of {
($state:ident, {$($(@#[$meta:meta])? $parser:expr => $constructor:expr,)+}) => ({
$(
$(#[$meta])?
{
let parser_result = $parser.parse($state).map(|(state, node)| (state, $constructor(node)));
if parser_result != Err(InternalAstError::NoMatch) {
return parser_result;
}
}
)+
Err(InternalAstError::NoMatch)
});
}
#[doc(hidden)]
#[macro_export]
macro_rules! expect {
($state:ident, $parsed:expr) => {
match $parsed {
Ok((state, node)) => (state, node),
Err(InternalAstError::NoMatch) => {
return Err(InternalAstError::UnexpectedToken {
token: $state.peek().clone(),
additional: None,
});
}
Err(other) => return Err(other),
}
};
($state:ident, $parsed:expr, $error:tt) => {
match $parsed {
Ok((state, node)) => (state, node),
Err(InternalAstError::NoMatch) => {
return Err(InternalAstError::UnexpectedToken {
token: $state.peek().clone(),
additional: Some(Cow::from($error)),
});
}
Err(other) => return Err(other),
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! keep_going {
($parsed:expr) => {
match $parsed {
Ok((state, node)) => Ok((state, node)),
Err(InternalAstError::NoMatch) => Err(InternalAstError::NoMatch),
Err(other) => return Err(other),
}
};
}
#[doc(hidden)]
#[macro_export]
#[rustfmt::skip]
macro_rules! define_roblox_parser {
($parser:ident, $node:ty, $mock_ty:ty, |$self:ident, $state:ident| $body:expr) => {
define_roblox_parser! ($parser, $node, $mock_ty, |$self:&$parser, mut $state: ParserState<'a>| $body);
};
($parser:ident, $node:ty, $mock_ty:ty, $body:expr) => {
cfg_if::cfg_if! {
if #[cfg(feature = "roblox")] {
define_parser!($parser, $node, $body);
} else {
define_parser!($parser, $mock_ty, |_, _| {
Err(InternalAstError::NoMatch)
});
}
}
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! define_lua52_parser {
($parser:ident, $node:ty, $mock_ty:ty, $body:expr) => {
cfg_if::cfg_if! {
if #[cfg(feature = "lua52")] {
define_parser!($parser, $node, $body);
} else {
define_parser!($parser, $mock_ty, |_, _| {
Err(InternalAstError::NoMatch)
});
}
}
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum InternalAstError {
NoMatch,
UnexpectedToken {
token: TokenReference,
additional: Option<Cow<'static, str>>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ZeroOrMore<P>(pub P);
impl<P, T> Parser for ZeroOrMore<P>
where
P: Parser<Item = T>,
{
type Item = Vec<T>;
fn parse<'a>(
&self,
mut state: ParserState<'a>,
) -> Result<(ParserState<'a>, Vec<T>), InternalAstError> {
let mut nodes = Vec::new();
loop {
match self.0.parse(state) {
Ok((new_state, node)) => {
state = new_state;
nodes.push(node);
}
Err(InternalAstError::NoMatch) => break,
Err(other) => return Err(other),
};
}
Ok((state, nodes))
}
}
macro_rules! test_pairs_logic {
($nodes:expr, $cause:expr) => {
if cfg!(debug_assertions) {
let len = $nodes.len();
for (index, node) in $nodes.pairs().enumerate() {
if index + 1 == len && node.punctuation().is_some() {
panic!(
"{} pairs illogical: last node has punctuation: {:?}",
$cause,
node.punctuation().unwrap()
);
} else if index + 1 != len && node.punctuation().is_none() {
panic!(
"{} pairs illogical: non-last node ({}) has no punctuation",
$cause, index
);
}
}
}
};
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ZeroOrMoreDelimited<ItemParser, Delimiter>(
pub ItemParser, pub Delimiter, pub bool, );
#[allow(clippy::blocks_in_if_conditions)]
#[allow(clippy::nonminimal_bool)]
impl<ItemParser, Delimiter, T> Parser for ZeroOrMoreDelimited<ItemParser, Delimiter>
where
ItemParser: Parser<Item = T>,
Delimiter: Parser<Item = TokenReference>,
T: Node + Visit + VisitMut,
{
type Item = Punctuated<T>;
fn parse<'a>(
&self,
mut state: ParserState<'a>,
) -> Result<(ParserState<'a>, Punctuated<T>), InternalAstError> {
let mut nodes = Punctuated::new();
if let Ok((new_state, node)) = keep_going!(self.0.parse(state)) {
state = new_state;
nodes.push(Pair::End(node));
} else {
return Ok((state, Punctuated::new()));
}
while let Ok((new_state, delimiter)) = keep_going!(self.1.parse(state)) {
let last_value = nodes.pop().unwrap().into_value();
nodes.push(Pair::Punctuated(last_value, delimiter));
state = new_state;
match self.0.parse(state) {
Ok((new_state, node)) => {
state = new_state;
nodes.push(Pair::End(node));
}
Err(InternalAstError::NoMatch) => {
if self.2 {
break;
}
return Err(InternalAstError::UnexpectedToken {
token: state.peek().clone(),
additional: Some(Cow::from("trailing character")),
});
}
Err(other) => {
return Err(other);
}
}
}
if !self.2 {
test_pairs_logic!(nodes, "ZeroOrMoreDelimited");
}
Ok((state, nodes))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct OneOrMore<ItemParser, Delimiter>(
pub ItemParser, pub Delimiter, pub bool, );
#[allow(clippy::blocks_in_if_conditions)]
#[allow(clippy::nonminimal_bool)]
impl<ItemParser, Delimiter, T> Parser for OneOrMore<ItemParser, Delimiter>
where
ItemParser: Parser<Item = T>,
Delimiter: Parser<Item = TokenReference>,
T: Node + Visit + VisitMut,
{
type Item = Punctuated<ItemParser::Item>;
fn parse<'a>(
&self,
state: ParserState<'a>,
) -> Result<(ParserState<'a>, Punctuated<ItemParser::Item>), InternalAstError> {
let mut nodes = Punctuated::new();
let (mut state, node) = self.0.parse(state)?;
nodes.push(Pair::End(node));
while let Ok((new_state, delimiter)) = self.1.parse(state) {
let last_value = nodes.pop().unwrap().into_value();
nodes.push(Pair::Punctuated(last_value, delimiter));
match self.0.parse(new_state) {
Ok((new_state, node)) => {
state = new_state;
nodes.push(Pair::End(node));
}
Err(InternalAstError::NoMatch) => {
if self.2 {
state = new_state;
}
break;
}
Err(other) => {
return Err(other);
}
}
}
if !self.2 {
let last_value = nodes.pop().unwrap().into_value();
nodes.push(Pair::End(last_value));
test_pairs_logic!(nodes, "OneOrMore");
}
Ok((state, nodes))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NoDelimiter;
define_parser!(NoDelimiter, (), |_, state| Ok((state, ())));