1use sim_codec::{DecodeBudget, DecodeLimits};
2use sim_kernel::{
3 CodecId, Error, Expr, Fixity, LocatedExprTree, Origin, PrattOperator, PrattTable,
4 PrattToken as Token, Result, SourceId, Span, Symbol, Trivia,
5 parse_pratt_symbol as parse_symbol,
6};
7
8use crate::{PrattTokenSource, SpannedPrattToken};
9
10pub fn raw_number_tag() -> Symbol {
12 Symbol::qualified("codec", "algol-number-literal")
13}
14
15pub fn raw_number_expr(raw: String) -> Expr {
17 Expr::Extension {
18 tag: raw_number_tag(),
19 payload: Box::new(Expr::String(raw)),
20 }
21}
22
23pub struct PrattCodecParser<S> {
25 operators: PrattTable,
26 token_source: S,
27 surface_name: &'static str,
28}
29
30impl<S: PrattTokenSource> PrattCodecParser<S> {
31 pub fn new(operators: PrattTable, token_source: S) -> Self {
33 Self {
34 operators,
35 token_source,
36 surface_name: "pratt",
37 }
38 }
39
40 pub fn with_surface_name(mut self, surface_name: &'static str) -> Self {
42 self.surface_name = surface_name;
43 self
44 }
45
46 pub fn operators(&self) -> &PrattTable {
48 &self.operators
49 }
50
51 pub fn token_source(&self) -> &S {
53 &self.token_source
54 }
55
56 pub fn parse_tree(&self, codec: CodecId, source: &str) -> Result<LocatedExprTree> {
58 let mut budget = DecodeBudget::new(DecodeLimits::default());
59 self.parse_tree_with_budget(codec, source, &mut budget)
60 }
61
62 pub fn parse_tree_with_budget(
64 &self,
65 codec: CodecId,
66 source: &str,
67 budget: &mut DecodeBudget,
68 ) -> Result<LocatedExprTree> {
69 self.parse_tree_with_source_and_budget(
70 codec,
71 SourceId(format!("<{}>", self.surface_name)),
72 source,
73 budget,
74 )
75 }
76
77 pub fn parse_tree_with_source(
79 &self,
80 codec: CodecId,
81 source_id: impl Into<String>,
82 source: &str,
83 ) -> Result<LocatedExprTree> {
84 let mut budget = DecodeBudget::new(DecodeLimits::default());
85 self.parse_tree_with_source_and_budget(
86 codec,
87 SourceId(source_id.into()),
88 source,
89 &mut budget,
90 )
91 }
92
93 pub fn parse_tree_with_source_and_budget(
95 &self,
96 codec: CodecId,
97 source_id: SourceId,
98 source: &str,
99 budget: &mut DecodeBudget,
100 ) -> Result<LocatedExprTree> {
101 let tokens = self.token_source.tokenize_pratt(codec, source, budget)?;
102 budget.check_tokens(codec, tokens.len())?;
103 let mut cx = ParseCx::new(tokens, self.surface_name);
104 let expr = self.parse_expr_tree(&mut cx, codec, &source_id, source, 0, budget, 0)?;
105 if !cx.is_empty() {
106 return Err(Error::Eval(format!(
107 "trailing {} tokens",
108 self.surface_name
109 )));
110 }
111 Ok(expr)
112 }
113
114 #[allow(clippy::too_many_arguments)]
115 pub(super) fn parse_expr_tree(
116 &self,
117 cx: &mut ParseCx,
118 codec: CodecId,
119 source_id: &SourceId,
120 source: &str,
121 min_bp: u16,
122 budget: &mut DecodeBudget,
123 depth: usize,
124 ) -> Result<LocatedExprTree> {
125 budget.enter_node(codec, depth)?;
126 let mut left = self.parse_nud_tree(cx, codec, source_id, source, budget, depth)?;
127
128 loop {
129 if matches!(
130 cx.peek(),
131 Some(SpannedPrattToken {
132 token: Token::OpenParen,
133 ..
134 })
135 ) {
136 if 110 < min_bp {
137 break;
138 }
139 let open = cx.next_required()?;
140 left = self.parse_call_tree(
141 cx, codec, source_id, source, left, open.start, budget, depth,
142 )?;
143 continue;
144 }
145
146 let Some(token) = cx.peek().cloned() else {
147 break;
148 };
149
150 let Some(op) = self.operators.lookup_led(&token.token) else {
151 break;
152 };
153
154 if op.fixity == Fixity::Postfix {
155 if op.left_bp < min_bp {
156 break;
157 }
158 cx.advance();
159 left = self.parse_postfix_tree(
160 codec, source_id, source, left, op, token.end, budget, depth,
161 )?;
162 continue;
163 }
164
165 if op.left_bp < min_bp {
166 break;
167 }
168
169 cx.advance();
170 left = self.parse_led_tree(cx, codec, source_id, source, left, op, budget, depth)?;
171 }
172
173 Ok(left)
174 }
175
176 fn parse_nud_tree(
177 &self,
178 cx: &mut ParseCx,
179 codec: CodecId,
180 source_id: &SourceId,
181 source: &str,
182 budget: &mut DecodeBudget,
183 depth: usize,
184 ) -> Result<LocatedExprTree> {
185 let token = cx.next_required()?;
186
187 if let Some(op) = self.operators.lookup_nud(&token.token) {
188 let right =
189 self.parse_expr_tree(cx, codec, source_id, source, op.right_bp, budget, depth + 1)?;
190 return self.build_prefix_tree(
191 codec,
192 source_id,
193 source,
194 op,
195 token.start,
196 right,
197 budget,
198 depth,
199 );
200 }
201
202 match token.token {
203 Token::Ident(name) => {
204 budget.enter_node(codec, depth)?;
205 let expr = match name.as_str() {
206 "nil" => Expr::Nil,
207 "true" => Expr::Bool(true),
208 "false" => Expr::Bool(false),
209 _ => Expr::Symbol(parse_symbol(&name)),
210 };
211 Ok(LocatedExprTree::without_children(
212 expr,
213 Some(tree_origin(
214 codec,
215 source_id.clone(),
216 source,
217 token.start,
218 token.end,
219 token.leading_trivia,
220 )),
221 ))
222 }
223 Token::Number(number) => {
224 budget.enter_node(codec, depth)?;
225 Ok(LocatedExprTree::without_children(
226 raw_number_expr(number),
227 Some(tree_origin(
228 codec,
229 source_id.clone(),
230 source,
231 token.start,
232 token.end,
233 token.leading_trivia,
234 )),
235 ))
236 }
237 Token::String(value) => {
238 budget.enter_node(codec, depth)?;
239 budget.check_string_bytes(codec, value.len())?;
240 Ok(LocatedExprTree::without_children(
241 Expr::String(value),
242 Some(tree_origin(
243 codec,
244 source_id.clone(),
245 source,
246 token.start,
247 token.end,
248 token.leading_trivia,
249 )),
250 ))
251 }
252 Token::OpenParen => {
253 let mut expr =
254 self.parse_expr_tree(cx, codec, source_id, source, 0, budget, depth + 1)?;
255 let close = cx.next_required()?;
256 if close.token != Token::CloseParen {
257 return Err(Error::Eval(format!(
258 "expected ')' in {} input, found {:?}",
259 self.surface_name, close
260 )));
261 }
262 extend_tree_trivia(&mut expr, close.leading_trivia.clone());
263 Ok(with_origin_span(
264 expr,
265 tree_origin(codec, source_id.clone(), source, token.start, close.end, {
266 let mut trivia = token.leading_trivia;
267 trivia.extend(close.leading_trivia);
268 trivia
269 }),
270 ))
271 }
272 other => Err(Error::Eval(format!(
273 "unexpected {} token in nud {:?}",
274 self.surface_name, other
275 ))),
276 }
277 }
278
279 #[allow(clippy::too_many_arguments)]
280 fn parse_led_tree(
281 &self,
282 cx: &mut ParseCx,
283 codec: CodecId,
284 source_id: &SourceId,
285 source: &str,
286 left: LocatedExprTree,
287 op: PrattOperator,
288 budget: &mut DecodeBudget,
289 depth: usize,
290 ) -> Result<LocatedExprTree> {
291 match op.fixity {
292 Fixity::InfixLeft | Fixity::InfixRight => {
293 let right = self.parse_expr_tree(
294 cx,
295 codec,
296 source_id,
297 source,
298 op.right_bp,
299 budget,
300 depth + 1,
301 )?;
302 self.build_infix_tree(codec, source_id, source, op, left, right, budget, depth)
303 }
304 _ => Err(Error::Eval("operator cannot be used here".to_owned())),
305 }
306 }
307}
308
309pub(super) struct ParseCx {
310 tokens: Vec<SpannedPrattToken>,
311 index: usize,
312 surface_name: &'static str,
313}
314
315impl ParseCx {
316 pub(super) fn new(tokens: Vec<SpannedPrattToken>, surface_name: &'static str) -> Self {
317 Self {
318 tokens,
319 index: 0,
320 surface_name,
321 }
322 }
323
324 pub(super) fn peek(&self) -> Option<&SpannedPrattToken> {
325 self.tokens.get(self.index)
326 }
327
328 pub(super) fn advance(&mut self) -> Option<SpannedPrattToken> {
329 let token = self.tokens.get(self.index).cloned()?;
330 self.index += 1;
331 Some(token)
332 }
333
334 pub(super) fn next_required(&mut self) -> Result<SpannedPrattToken> {
335 self.advance()
336 .ok_or_else(|| Error::Eval(format!("unexpected end of {} input", self.surface_name)))
337 }
338
339 pub(super) fn is_empty(&self) -> bool {
340 self.index >= self.tokens.len()
341 }
342}
343
344pub(super) fn with_origin_span(mut tree: LocatedExprTree, origin: Origin) -> LocatedExprTree {
345 tree.origin = Some(origin);
346 tree
347}
348
349pub(super) fn extend_tree_trivia(tree: &mut LocatedExprTree, trivia: Vec<Trivia>) {
350 if trivia.is_empty() {
351 return;
352 }
353 if let Some(origin) = &mut tree.origin {
354 origin.trivia.extend(trivia);
355 }
356}
357
358pub(super) fn tree_origin(
359 codec: CodecId,
360 source: SourceId,
361 _raw: &str,
362 start: usize,
363 end: usize,
364 trivia: Vec<Trivia>,
365) -> Origin {
366 Origin {
367 codec,
368 source,
369 span: Span { start, end },
370 trivia,
371 }
372}