1#[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}