Skip to main content

litcheck_filecheck/expr/
parser.rs

1/// Simple macro used in the grammar definition for constructing spans
2#[macro_export]
3macro_rules! span {
4    ($source_id:expr, $l:expr, $r:expr) => {
5        litcheck::diagnostics::SourceSpan::new(
6            $source_id,
7            litcheck::range::Range::new($l as u32, $r as u32),
8        )
9    };
10    ($source_id:expr, $i:expr) => {
11        litcheck::diagnostics::SourceSpan::at($source_id, $i as u32)
12    };
13}
14
15lalrpop_util::lalrpop_mod!(
16    #[allow(clippy::all)]
17    grammar,
18    "/expr/grammar.rs"
19);
20
21use std::fmt;
22
23use logos::{Lexer, Logos};
24
25use litcheck::diagnostics::{Report, SourceId, SourceSpan, Span};
26
27use crate::expr::{ExprError, Variable};
28
29pub type ParseError<'a> = lalrpop_util::ParseError<usize, Token<'a>, ExprError>;
30
31pub struct NumericVarParser;
32
33impl NumericVarParser {
34    pub fn parse<'a>(&mut self, source: Span<&'a str>) -> Result<Variable<'a>, Report> {
35        let (span, source) = source.into_parts();
36        let lexer = Token::lexer(source)
37            .spanned()
38            .map(|(t, span)| t.map(|t| (span.start, t, span.end)));
39        grammar::NumericVarParser::new()
40            .parse(span.source_id(), source, lexer)
41            .map_err(|err| handle_parse_error(span.source_id(), err))
42            .map_err(|err| Report::from(err).with_source_code(source.to_string()))
43    }
44}
45
46fn handle_parse_error(source_id: SourceId, err: ParseError) -> ExprError {
47    match err {
48        ParseError::InvalidToken { location: at } => ExprError::InvalidToken {
49            span: SourceSpan::at(source_id, at as u32),
50        },
51        ParseError::UnrecognizedToken {
52            token: (l, tok, r),
53            expected,
54        } => ExprError::UnrecognizedToken {
55            span: SourceSpan::from_range_unchecked(source_id, l..r),
56            token: tok.to_string(),
57            expected,
58        },
59        ParseError::ExtraToken { token: (l, tok, r) } => ExprError::ExtraToken {
60            span: SourceSpan::from_range_unchecked(source_id, l..r),
61            token: tok.to_string(),
62        },
63        ParseError::UnrecognizedEof {
64            location: at,
65            expected,
66        } => ExprError::UnrecognizedEof {
67            span: SourceSpan::at(source_id, at as u32),
68            expected,
69        },
70        ParseError::User { error } => error,
71    }
72}
73
74#[derive(Debug, Clone, Logos)]
75#[logos(error = ExprError)]
76#[logos(extras = SourceId)]
77#[logos(skip r"[ \t]+")]
78pub enum Token<'input> {
79    #[token("(")]
80    LParen,
81    #[token(")")]
82    RParen,
83    #[token("#")]
84    Hash,
85    #[token("$")]
86    Dollar,
87    #[token("%")]
88    Percent,
89    #[token("@")]
90    At,
91    #[token(",")]
92    Comma,
93    #[token(".")]
94    Dot,
95    #[token(":")]
96    Colon,
97    #[token("+")]
98    Plus,
99    #[token("-")]
100    Minus,
101    #[token("=")]
102    Equal,
103    #[token("==")]
104    Equals,
105    #[token("add")]
106    Add,
107    #[token("sub")]
108    Sub,
109    #[token("mul")]
110    Mul,
111    #[token("div")]
112    Div,
113    #[token("min")]
114    Min,
115    #[token("max")]
116    Max,
117    #[regex(r"[A-Za-z_][A-Za-z0-9_]*", |lex| lex.slice())]
118    Ident(&'input str),
119    #[regex(r"-?[0-9]+", parse_int)]
120    Num(i128),
121}
122impl<'input> Eq for Token<'input> {}
123impl<'input> PartialEq for Token<'input> {
124    fn eq(&self, other: &Token<'input>) -> bool {
125        match (self, other) {
126            (Self::Ident(a), Self::Ident(b)) => a == b,
127            (Self::Num(a), Self::Num(b)) => a == b,
128            (a, b) => core::mem::discriminant(a) == core::mem::discriminant(b),
129        }
130    }
131}
132impl<'input> fmt::Display for Token<'input> {
133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134        use std::fmt::Write;
135        match self {
136            Self::Ident(s) => f.write_str(s),
137            Self::Num(n) => write!(f, "{n}"),
138            Self::LParen => f.write_char('('),
139            Self::RParen => f.write_char(')'),
140            Self::Hash => f.write_char('#'),
141            Self::Dollar => f.write_char('$'),
142            Self::Percent => f.write_char('%'),
143            Self::At => f.write_char('@'),
144            Self::Colon => f.write_char(':'),
145            Self::Comma => f.write_char(','),
146            Self::Dot => f.write_char('.'),
147            Self::Plus => f.write_char('+'),
148            Self::Minus => f.write_char('-'),
149            Self::Equal => f.write_str("="),
150            Self::Equals => f.write_str("=="),
151            Self::Add => f.write_str("add"),
152            Self::Sub => f.write_str("sub"),
153            Self::Mul => f.write_str("mul"),
154            Self::Div => f.write_str("div"),
155            Self::Min => f.write_str("min"),
156            Self::Max => f.write_str("max"),
157        }
158    }
159}
160
161fn parse_int<'input>(lexer: &mut Lexer<'input, Token<'input>>) -> Result<i128, ExprError> {
162    lexer
163        .slice()
164        .parse::<i128>()
165        .map_err(|error| ExprError::Number {
166            span: SourceSpan::try_from_range(lexer.extras, lexer.span()).unwrap(),
167            error,
168        })
169}