use crate::{CalcLexer, CalcToken, SymTab, TokenID, TokenValue};
use parlex::{
LexerStats, ParlexError, Parser, ParserAction, ParserData, ParserDriver, ParserStats, Token,
};
use parser_data::{AmbigID, ParData, ProdID, StateID};
use std::marker::PhantomData;
use try_next::TryNextWithContext;
pub mod parser_data {
include!(concat!(env!("OUT_DIR"), "/parser_data.rs"));
}
pub struct CalcParserDriver<I> {
_marker: PhantomData<I>,
}
impl<I> ParserDriver for CalcParserDriver<I>
where
I: TryNextWithContext<SymTab, LexerStats, Item = CalcToken, Error: std::fmt::Display + 'static>,
{
type ParserData = ParData;
type Token = CalcToken;
type Parser = Parser<I, Self, Self::Context>;
type Context = SymTab;
fn resolve_ambiguity(
&mut self,
_parser: &mut Self::Parser,
_context: &mut Self::Context,
ambig: <Self::ParserData as ParserData>::AmbigID,
token: &Self::Token,
) -> Result<ParserAction<StateID, ProdID, AmbigID>, ParlexError> {
let ambig_tab = ParData::lookup_ambig(ambig);
let shift = ambig_tab[0];
let reduce = ambig_tab[1];
let ParserAction::Shift(_) = shift else {
panic!("expected shift");
};
let ParserAction::Reduce(prod_id) = reduce else {
panic!("expected reduce");
};
let CalcToken { token_id, .. } = token;
match prod_id {
ProdID::Expr3 | ProdID::Expr4 => {
match token_id {
TokenID::Plus | TokenID::Minus => Ok(reduce), TokenID::Asterisk | TokenID::Slash => Ok(shift), _ => unreachable!(),
}
}
ProdID::Expr5 | ProdID::Expr6 => {
Ok(reduce) }
ProdID::Expr7 => {
Ok(reduce) }
_ => panic!("unexpected prod in ambiguity"),
}
}
fn reduce(
&mut self,
parser: &mut Self::Parser,
context: &mut Self::Context,
prod_id: <Self::ParserData as ParserData>::ProdID,
token: &Self::Token,
) -> Result<(), ParlexError> {
match prod_id {
ProdID::Start => {
unreachable!()
}
ProdID::Stat1 => {
parser.tokens_push(CalcToken {
token_id: TokenID::Stat,
span: token.span(),
value: TokenValue::None,
});
}
ProdID::Stat2 => {
let mut stat = parser.tokens_pop();
let comment_tok = parser.tokens_pop();
let TokenValue::Comment(comment) = comment_tok.value else {
unreachable!()
};
stat.to_statement(Some(comment));
stat.merge_span(&comment_tok.span);
parser.tokens_push(stat);
}
ProdID::Stat3 => {
let mut expr = parser.tokens_pop();
expr.token_id = TokenID::Stat;
parser.tokens_push(expr);
}
ProdID::Stat4 => {
let mut expr = parser.tokens_pop();
let TokenValue::Number(value) = expr.value else {
unreachable!()
};
parser.tokens_pop();
let ident = parser.tokens_pop();
let TokenValue::Ident(index) = ident.value else {
unreachable!()
};
context
.set(index, value)
.map_err(|e| ParlexError::from_err(e, ident.span()))?; expr.token_id = TokenID::Stat;
expr.merge_span(&ident.span);
parser.tokens_push(expr);
}
ProdID::Expr1 => {
let mut number = parser.tokens_pop();
number.token_id = TokenID::Expr;
parser.tokens_push(number);
}
ProdID::Expr2 => {
let mut tok = parser.tokens_pop();
tok.token_id = TokenID::Expr;
let TokenValue::Ident(index) = tok.value else {
unreachable!()
};
tok.value = TokenValue::Number(
context
.get(index)
.map_err(|e| ParlexError::from_err(e, tok.span()))?,
);
parser.tokens_push(tok);
}
ProdID::Expr3 => {
let expr2 = parser.tokens_pop();
parser.tokens_pop();
let mut expr1 = parser.tokens_pop();
let TokenValue::Number(value1) = expr1.value else {
unreachable!()
};
let TokenValue::Number(value2) = expr2.value else {
unreachable!()
};
expr1.value = TokenValue::Number(value1 + value2);
expr1.merge_span(&expr2.span);
parser.tokens_push(expr1);
}
ProdID::Expr4 => {
let expr2 = parser.tokens_pop();
parser.tokens_pop();
let mut expr1 = parser.tokens_pop();
let TokenValue::Number(value1) = expr1.value else {
unreachable!()
};
let TokenValue::Number(value2) = expr2.value else {
unreachable!()
};
expr1.value = TokenValue::Number(value1 - value2);
expr1.merge_span(&expr2.span);
parser.tokens_push(expr1);
}
ProdID::Expr5 => {
let expr2 = parser.tokens_pop();
parser.tokens_pop();
let mut expr1 = parser.tokens_pop();
let TokenValue::Number(value1) = expr1.value else {
unreachable!()
};
let TokenValue::Number(value2) = expr2.value else {
unreachable!()
};
expr1.value = TokenValue::Number(value1 * value2);
expr1.merge_span(&expr2.span);
parser.tokens_push(expr1);
}
ProdID::Expr6 => {
let expr2 = parser.tokens_pop();
parser.tokens_pop();
let mut expr1 = parser.tokens_pop();
let TokenValue::Number(value1) = expr1.value else {
unreachable!()
};
let TokenValue::Number(value2) = expr2.value else {
unreachable!()
};
expr1.value = TokenValue::Number(value1 / value2);
expr1.merge_span(&expr2.span);
parser.tokens_push(expr1);
}
ProdID::Expr7 => {
let mut expr = parser.tokens_pop();
let minus = parser.tokens_pop();
let TokenValue::Number(value) = expr.value else {
unreachable!()
};
expr.value = TokenValue::Number(-value);
expr.merge_span(&minus.span);
parser.tokens_push(expr);
}
ProdID::Expr8 => {
let left_paren = parser.tokens_pop();
let mut expr = parser.tokens_pop();
let right_paren = parser.tokens_pop();
expr.merge_span(&left_paren.span);
expr.merge_span(&right_paren.span);
parser.tokens_push(expr);
}
}
Ok(())
}
}
pub struct CalcParser<I>
where
I: TryNextWithContext<SymTab, Item = u8, Error: std::fmt::Display + 'static>,
{
parser: Parser<CalcLexer<I>, CalcParserDriver<CalcLexer<I>>, SymTab>,
}
impl<I> CalcParser<I>
where
I: TryNextWithContext<SymTab, Item = u8, Error: std::fmt::Display + 'static>,
{
pub fn try_new(input: I) -> Result<Self, ParlexError> {
let lexer = CalcLexer::try_new(input)?;
let driver = CalcParserDriver {
_marker: PhantomData,
};
let parser = Parser::new(lexer, driver);
Ok(Self { parser })
}
}
impl<I> TryNextWithContext<SymTab, (LexerStats, ParserStats)> for CalcParser<I>
where
I: TryNextWithContext<SymTab, Item = u8, Error: std::fmt::Display + 'static>,
{
type Item = CalcToken;
type Error = ParlexError;
fn try_next_with_context(
&mut self,
context: &mut SymTab,
) -> Result<Option<CalcToken>, ParlexError> {
self.parser.try_next_with_context(context)
}
fn stats(&self) -> (LexerStats, ParserStats) {
self.parser.stats()
}
}
#[cfg(test)]
mod tests {
use crate::{CalcParser, CalcToken, SymTab, TokenID, TokenValue};
use parlex::span;
use try_next::{IterInput, TryNextWithContext};
#[test]
fn parses_four_stats() {
let _ = env_logger::builder().is_test(true).try_init();
let mut symtab = SymTab::new();
let input = IterInput::from(
"hello = 1;\n 1 + 2;\n (world + hello + 10) * -2;\n\n1000 - - -123;".bytes(),
);
let mut parser = CalcParser::try_new(input).unwrap();
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
Some(CalcToken {
token_id: TokenID::Stat,
span: span!(0, 0, 0, 9),
value: TokenValue::Number(1)
}),
));
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
Some(CalcToken {
token_id: TokenID::Stat,
span: span!(1, 1, 1, 6),
value: TokenValue::Number(3)
}),
));
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
Some(CalcToken {
token_id: TokenID::Stat,
span: span!(2, 1, 2, 26),
value: TokenValue::Number(-22)
}),
));
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
Some(CalcToken {
token_id: TokenID::Stat,
span: span!(4, 0, 4, 13),
value: TokenValue::Number(877)
}),
));
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
Some(CalcToken {
token_id: TokenID::Stat,
span: span!(4, 14, 4, 14),
value: TokenValue::None
}),
));
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
None,
));
assert!(matches!(
parser.try_next_with_context(&mut symtab).unwrap(),
None,
));
}
#[test]
fn parses_assignment_and_reference() {
let _ = env_logger::builder().is_test(true).try_init();
let mut symtab = SymTab::new();
let input = IterInput::from("x = 2;\n x + 3;".bytes());
let mut parser = CalcParser::try_new(input).unwrap();
let t1 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t1,
CalcToken {
token_id: TokenID::Stat,
span: span!(0, 0, 0, 5),
value: TokenValue::Number(2)
}
));
let t2 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t2,
CalcToken {
token_id: TokenID::Stat,
span: span!(1, 1, 1, 6),
value: TokenValue::Number(5)
}
));
let t3 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t3,
CalcToken {
token_id: TokenID::Stat,
span: span!(1, 7, 1, 7),
value: TokenValue::None
}
));
assert!(parser.try_next_with_context(&mut symtab).unwrap().is_none());
assert_eq!(symtab.len(), 1);
}
#[test]
fn respects_operator_precedence_and_unary_minus() {
let _ = env_logger::builder().is_test(true).try_init();
let mut symtab = SymTab::new();
let input = IterInput::from("1 + 2 * 3;\n-(1 + 2) * 3".bytes());
let mut parser = CalcParser::try_new(input).unwrap();
let t1 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
dbg!(parser.stats());
assert!(matches!(
t1,
CalcToken {
token_id: TokenID::Stat,
span: span!(0, 0, 0, 9),
value: TokenValue::Number(7)
}
));
let t2 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t2,
CalcToken {
token_id: TokenID::Stat,
span: span!(1, 0, 1, 12),
value: TokenValue::Number(-9)
}
));
assert!(parser.try_next_with_context(&mut symtab).unwrap().is_none());
}
#[test]
fn emits_empty_statement_as_none() {
let _ = env_logger::builder().is_test(true).try_init();
let mut symtab = SymTab::new();
let input = IterInput::from("1; ;".bytes());
let mut parser = CalcParser::try_new(input).unwrap();
let t1 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t1,
CalcToken {
token_id: TokenID::Stat,
value: TokenValue::Number(1),
..
}
));
let t2 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t2,
CalcToken {
token_id: TokenID::Stat,
value: TokenValue::None,
..
}
));
let t3 = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t3,
CalcToken {
token_id: TokenID::Stat,
value: TokenValue::None,
..
}
));
assert!(parser.try_next_with_context(&mut symtab).unwrap().is_none());
}
#[test]
fn parentheses_override_precedence() {
let _ = env_logger::builder().is_test(true).try_init();
let mut symtab = SymTab::new();
let input = IterInput::from("(1 + 2) * 3".bytes());
let mut parser = CalcParser::try_new(input).unwrap();
let t = parser.try_next_with_context(&mut symtab).unwrap().unwrap();
assert!(matches!(
t,
CalcToken {
token_id: TokenID::Stat,
value: TokenValue::Number(9),
..
}
));
assert!(parser.try_next_with_context(&mut symtab).unwrap().is_none());
}
}