raw
1use std::borrow::Cow;
2
3use chumsky::{
4 input::MappedInput,
5 pratt::{infix, left, postfix, prefix},
6 prelude::*,
7};
8
9use super::ast::{BinOp, Expr, Ident, Token, UnOp};
10
11pub fn lexer<'src>()
12-> impl Parser<'src, &'src str, Vec<Spanned<Token<'src>>>, extra::Err<Rich<'src, char>>> {
13 recursive(|_token| {
14 choice((
15 text::ident().map(|s| match s {
16 "true" => Token::Bool(true),
17 "false" => Token::Bool(false),
18 s => Token::Ident(Ident(Cow::from(s))),
19 }),
20 just('"')
21 .ignore_then(none_of('"').repeated().to_slice())
22 .then_ignore(just('"'))
23 .map(|s| Token::String(Cow::from(s))),
24 just("+").to(Token::Add),
25 just("-").to(Token::Sub),
26 just("*").to(Token::Mul),
27 just("/").to(Token::Div),
28 just("%").to(Token::Mod),
29 just("==").to(Token::Equal),
30 just("!=").to(Token::NotEqual),
31 just(">").to(Token::GreaterThan),
32 just("<").to(Token::LessThan),
33 just(">=").to(Token::GreaterEqual),
34 just("<=").to(Token::LessEqual),
35 just("!").to(Token::Not),
36 just("&&").to(Token::And),
37 just("||").to(Token::Or),
38 just("(").to(Token::ParenOpen),
39 just(")").to(Token::ParenClose),
40 just("[").to(Token::BracketOpen),
41 just("]").to(Token::BracketClose),
42 just(",").to(Token::Comma),
43 just(".").to(Token::Period),
44 text::int(10)
45 .then(just('.').then(text::digits(10)).or_not())
46 .to_slice()
47 .from_str()
48 .unwrapped()
49 .map(Token::Decimal),
50 text::int(10)
51 .to_slice()
52 .from_str()
53 .unwrapped()
54 .map(Token::Integer),
55 ))
56 .spanned()
57 .padded()
58 })
59 .repeated()
60 .collect()
61}
62
63#[cfg(test)]
64mod test_lexer {
65 use chumsky::prelude::*;
66
67 use super::*;
68
69 #[rstest::rstest]
70 #[case(
71 r#"note["link-data"] == "fanfiction""#,
72 vec![
73 (Token::Ident(Ident("note".into())).with_span(SimpleSpan::from(0..4))),
74 (Token::BracketOpen.with_span(SimpleSpan::from(4..5))),
75 (Token::String("link-data".into()).with_span(SimpleSpan::from(5..16))),
76 (Token::BracketClose.with_span(SimpleSpan::from(16..17))),
77 (Token::Equal.with_span(SimpleSpan::from(18..20))),
78 (Token::String("fanfiction".into()).with_span(SimpleSpan::from(21..33))),
79 ],
80 )]
81 fn test(#[case] input: &str, #[case] expected: Vec<Spanned<Token<'_>>>) {
82 let tokens = lexer().parse(input);
83 assert!(!tokens.has_errors());
84 assert_eq!(tokens.into_output(), Some(expected));
85 }
86
87 #[test]
88 fn setup_test() {
89 let tokens = lexer().parse(r#"note["link-data"] == "fanfiction""#);
90 assert!(!tokens.has_errors());
91 assert_eq!(
92 tokens.into_output(),
93 Some(vec![
94 (Token::Ident(Ident("note".into())).with_span(SimpleSpan::from(0..4))),
95 (Token::BracketOpen.with_span(SimpleSpan::from(4..5))),
96 (Token::String("link-data".into()).with_span(SimpleSpan::from(5..16))),
97 (Token::BracketClose.with_span(SimpleSpan::from(16..17))),
98 (Token::Equal.with_span(SimpleSpan::from(18..20))),
99 (Token::String("fanfiction".into()).with_span(SimpleSpan::from(21..33))),
100 ])
101 );
102 }
103}
104
105pub fn parser<'tokens, 'src: 'tokens>() -> impl Parser<
106 'tokens,
107 MappedInput<'tokens, Token<'src>, SimpleSpan, &'tokens [Spanned<Token<'src>>]>,
108 Spanned<Expr<'src>>,
109 extra::Err<Rich<'tokens, Token<'src>>>,
110> {
111 recursive(|expr| {
112 let value = select! {
113 Token::Bool(b) => Expr::Bool(b),
114 Token::Integer(i) => Expr::Integer(i),
115 Token::Decimal(f) => Expr::Decimal(f),
116 Token::String(s) => Expr::String(s),
117 }
118 .labelled("value");
119
120 let ident = select! { Token::Ident(x) => x }.labelled("identifier");
121 let ident_expr = select! { Token::Ident(x) => Expr::Ident(x) }.labelled("identifier");
122
123 let params = expr
124 .clone()
125 .separated_by(just(Token::Comma))
126 .allow_trailing()
127 .collect::<Vec<_>>()
128 .delimited_by(just(Token::ParenOpen), just(Token::ParenClose))
129 .spanned();
130
131 let group = expr
132 .clone()
133 .delimited_by(just(Token::ParenOpen), just(Token::ParenClose))
134 .map(|inner| Expr::Group(Box::new(inner)))
135 .boxed();
136
137 let indexed = expr
138 .clone()
139 .delimited_by(just(Token::BracketOpen), just(Token::BracketClose));
140
141 choice((value, ident_expr, group)).spanned().pratt((
142 postfix(
143 16,
144 just(Token::Period)
145 .ignore_then(ident.spanned())
146 .then(params.clone()),
147 |name, (method, params), e| {
148 Expr::Method(Box::new(name), method, params).with_span(e.span())
149 },
150 ),
151 postfix(
152 15,
153 just(Token::Period).ignore_then(ident.spanned()),
154 |name, method, e| Expr::Member(Box::new(name), method).with_span(e.span()),
155 ),
156 postfix(14, params.clone(), |name, params, e| {
157 Expr::Function(Box::new(name), params).with_span(e.span())
158 }),
159 postfix(13, indexed, |name, params, e| {
160 Expr::Index(Box::new(name), Box::new(params)).with_span(e.span())
161 }),
162 infix(
164 left(12),
165 select! { Token::Or = e => BinOp::Or.with_span(e.span()) },
166 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
167 ),
168 infix(
169 left(11),
170 select! { Token::And = e => BinOp::And.with_span(e.span()) },
171 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
172 ),
173 infix(
175 left(7),
176 select! { Token::Equal = e => BinOp::Equal.with_span(e.span()) },
177 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
178 ),
179 infix(
180 left(7),
181 select! { Token::NotEqual = e => BinOp::NotEqual.with_span(e.span()) },
182 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
183 ),
184 infix(
186 left(6),
187 select! { Token::GreaterThan = e => BinOp::GreaterThan.with_span(e.span()) },
188 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
189 ),
190 infix(
191 left(6),
192 select! { Token::GreaterEqual = e => BinOp::GreaterEqual.with_span(e.span()) },
193 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
194 ),
195 infix(
196 left(6),
197 select! { Token::LessThan = e => BinOp::LessThan.with_span(e.span()) },
198 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
199 ),
200 infix(
201 left(6),
202 select! { Token::LessEqual = e => BinOp::LessEqual.with_span(e.span()) },
203 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
204 ),
205 infix(
207 left(4),
208 select! { Token::Add = e => BinOp::Add.with_span(e.span()) },
209 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
210 ),
211 infix(
212 left(4),
213 select! { Token::Sub = e => BinOp::Sub.with_span(e.span()) },
214 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
215 ),
216 infix(
218 left(3),
219 select! { Token::Mul = e => BinOp::Mul.with_span(e.span()) },
220 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
221 ),
222 infix(
223 left(3),
224 select! { Token::Div = e => BinOp::Div.with_span(e.span()) },
225 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
226 ),
227 infix(
228 left(3),
229 select! { Token::Mod = e => BinOp::Mod.with_span(e.span()) },
230 |x, op, y, e| Expr::BinOp(op, Box::new(x), Box::new(y)).with_span(e.span()),
231 ),
232 prefix(
234 2,
235 select! { Token::Sub = e => UnOp::Neg.with_span(e.span()) },
236 |op, x, e| Expr::UnOp(op, Box::new(x)).with_span(e.span()),
237 ),
238 prefix(
239 2,
240 select! { Token::Not = e => UnOp::Not.with_span(e.span()) },
241 |op, x, e| Expr::UnOp(op, Box::new(x)).with_span(e.span()),
242 ),
243 ))
244 })
245}
246
247#[cfg(test)]
248mod test_parser {
249 use chumsky::prelude::*;
250
251 use super::*;
252
253 #[test]
254 fn setup_test() {
255 let tokens = lexer().parse(r#"note["link-data"] == "fanfiction""#);
256 assert!(!tokens.has_errors());
257 assert_eq!(
258 tokens.into_output(),
259 Some(vec![
260 (Token::Ident(Ident("note".into())).with_span(SimpleSpan::from(0..4))),
261 (Token::BracketOpen.with_span(SimpleSpan::from(4..5))),
262 (Token::String("link-data".into()).with_span(SimpleSpan::from(5..16))),
263 (Token::BracketClose.with_span(SimpleSpan::from(16..17))),
264 (Token::Equal.with_span(SimpleSpan::from(18..20))),
265 (Token::String("fanfiction".into()).with_span(SimpleSpan::from(21..33))),
266 ])
267 );
268 }
269}
270