1use crate::parse::{
6 ParseCx, SpannedToken, extend_tree_trivia, raw_number_expr, tokenize_algol_spanned_with_budget,
7 tree_origin, with_origin_span,
8};
9use sim_codec::{DecodeBudget, DecodeLimits};
10use sim_kernel::{
11 Error, Expr, Fixity, LocatedExprTree, PrattOperator, PrattTable, PrattToken as Token, Result,
12 SourceId, parse_pratt_symbol as parse_symbol,
13};
14
15pub struct PrattParser {
19 pub(crate) operators: PrattTable,
20}
21
22impl PrattParser {
23 pub fn new(operators: PrattTable) -> Self {
26 Self { operators }
27 }
28
29 pub fn parse_text_tree(
32 &self,
33 codec: sim_kernel::CodecId,
34 source_id: impl Into<String>,
35 source: &str,
36 ) -> Result<LocatedExprTree> {
37 let mut budget = DecodeBudget::new(DecodeLimits::default());
38 self.parse_text_tree_with_budget(codec, source_id, source, &mut budget)
39 }
40
41 pub fn parse_text_tree_with_budget(
44 &self,
45 codec: sim_kernel::CodecId,
46 source_id: impl Into<String>,
47 source: &str,
48 budget: &mut DecodeBudget,
49 ) -> Result<LocatedExprTree> {
50 let tokens = tokenize_algol_spanned_with_budget(source, budget)?;
51 budget.check_tokens(codec, tokens.len())?;
52 let mut cx = ParseCx::new(tokens);
53 let expr = self.parse_expr_tree(
54 &mut cx,
55 codec,
56 &SourceId(source_id.into()),
57 source,
58 0,
59 budget,
60 0,
61 )?;
62 if !cx.is_empty() {
63 return Err(Error::Eval("trailing algol tokens".to_owned()));
64 }
65 Ok(expr)
66 }
67
68 #[allow(clippy::too_many_arguments)]
69 pub(super) fn parse_expr_tree(
70 &self,
71 cx: &mut ParseCx,
72 codec: sim_kernel::CodecId,
73 source_id: &SourceId,
74 source: &str,
75 min_bp: u16,
76 budget: &mut DecodeBudget,
77 depth: usize,
78 ) -> Result<LocatedExprTree> {
79 budget.enter_node(codec, depth)?;
85 let mut left = self.parse_nud_tree(cx, codec, source_id, source, budget, depth)?;
86
87 loop {
88 if matches!(
89 cx.peek(),
90 Some(SpannedToken {
91 token: Token::OpenParen,
92 ..
93 })
94 ) {
95 if 110 < min_bp {
96 break;
97 }
98 let open = cx.next_required()?;
99 left = self.parse_call_tree(
100 cx, codec, source_id, source, left, open.start, budget, depth,
101 )?;
102 continue;
103 }
104
105 let Some(token) = cx.peek().cloned() else {
106 break;
107 };
108
109 let Some(op) = self.operators.lookup_led(&token.token) else {
110 break;
111 };
112
113 if op.fixity == Fixity::Postfix {
114 if op.left_bp < min_bp {
115 break;
116 }
117 cx.advance();
118 left = self.parse_postfix_tree(
119 codec, source_id, source, left, op, token.end, budget, depth,
120 )?;
121 continue;
122 }
123
124 if op.left_bp < min_bp {
125 break;
126 }
127
128 cx.advance();
129 left = self.parse_led_tree(cx, codec, source_id, source, left, op, budget, depth)?;
130 }
131
132 Ok(left)
133 }
134
135 fn parse_nud_tree(
136 &self,
137 cx: &mut ParseCx,
138 codec: sim_kernel::CodecId,
139 source_id: &SourceId,
140 source: &str,
141 budget: &mut DecodeBudget,
142 depth: usize,
143 ) -> Result<LocatedExprTree> {
144 let token = cx.next_required()?;
145
146 if let Some(op) = self.operators.lookup_nud(&token.token) {
147 let right =
148 self.parse_expr_tree(cx, codec, source_id, source, op.right_bp, budget, depth + 1)?;
149 return self.build_prefix_tree(
150 codec,
151 source_id,
152 source,
153 op,
154 token.start,
155 right,
156 budget,
157 depth,
158 );
159 }
160
161 match token.token {
162 Token::Ident(name) => {
163 budget.enter_node(codec, depth)?;
164 let expr = match name.as_str() {
165 "nil" => Expr::Nil,
166 "true" => Expr::Bool(true),
167 "false" => Expr::Bool(false),
168 _ => Expr::Symbol(parse_symbol(&name)),
169 };
170 Ok(LocatedExprTree::without_children(
171 expr,
172 Some(tree_origin(
173 codec,
174 source_id.clone(),
175 source,
176 token.start,
177 token.end,
178 token.leading_trivia.clone(),
179 )),
180 ))
181 }
182 Token::Number(number) => {
183 budget.enter_node(codec, depth)?;
184 Ok(LocatedExprTree::without_children(
185 raw_number_expr(number),
186 Some(tree_origin(
187 codec,
188 source_id.clone(),
189 source,
190 token.start,
191 token.end,
192 token.leading_trivia.clone(),
193 )),
194 ))
195 }
196 Token::String(value) => {
197 budget.enter_node(codec, depth)?;
198 budget.check_string_bytes(codec, value.len())?;
199 Ok(LocatedExprTree::without_children(
200 Expr::String(value),
201 Some(tree_origin(
202 codec,
203 source_id.clone(),
204 source,
205 token.start,
206 token.end,
207 token.leading_trivia.clone(),
208 )),
209 ))
210 }
211 Token::OpenParen => {
212 let mut expr =
213 self.parse_expr_tree(cx, codec, source_id, source, 0, budget, depth + 1)?;
214 let close = cx.next_required()?;
215 if close.token != Token::CloseParen {
216 return Err(Error::Eval(format!(
217 "expected ')' in algol input, found {:?}",
218 close
219 )));
220 }
221 extend_tree_trivia(&mut expr, close.leading_trivia.clone());
222 Ok(with_origin_span(
223 expr,
224 tree_origin(codec, source_id.clone(), source, token.start, close.end, {
225 let mut trivia = token.leading_trivia.clone();
226 trivia.extend(close.leading_trivia.clone());
227 trivia
228 }),
229 ))
230 }
231 other => Err(Error::Eval(format!(
232 "unexpected algol token in nud {:?}",
233 other
234 ))),
235 }
236 }
237
238 #[allow(clippy::too_many_arguments)]
239 fn parse_led_tree(
240 &self,
241 cx: &mut ParseCx,
242 codec: sim_kernel::CodecId,
243 source_id: &SourceId,
244 source: &str,
245 left: LocatedExprTree,
246 op: PrattOperator,
247 budget: &mut DecodeBudget,
248 depth: usize,
249 ) -> Result<LocatedExprTree> {
250 match op.fixity {
251 Fixity::InfixLeft | Fixity::InfixRight => {
252 let right = self.parse_expr_tree(
253 cx,
254 codec,
255 source_id,
256 source,
257 op.right_bp,
258 budget,
259 depth + 1,
260 )?;
261 self.build_infix_tree(codec, source_id, source, op, left, right, budget, depth)
262 }
263 _ => Err(Error::Eval("operator cannot be used here".to_owned())),
264 }
265 }
266}