implicit_predicates_gen/point/
parse.rs1use proc_macro2::{Delimiter, LexError, Span, TokenStream, TokenTree};
25
26use super::expr::{Scope, Value, operator_span, parse_expr};
27use super::{
28 Constraint, LinearSum, ParamType, PointBuilder, PointGraph, PointNode, PointParam, PointType,
29};
30use crate::cursor::{Cursor, is_punct};
31use crate::error::{Diag, Error, Res, diag};
32use crate::expr::int_literal;
33use crate::limits;
34use crate::parse::{Position, can_name_a_module};
35
36pub fn parse_points(source: &str, file: &str) -> Result<Vec<PointType>, Error> {
60 parse_source(source).map_err(|d| Error {
61 file: file.to_string(),
62 line: d.line,
63 column: d.column,
64 message: d.message,
65 })
66}
67
68fn parse_source(source: &str) -> Res<Vec<PointType>> {
69 limits::check_text(source)?;
70 let tokens: TokenStream = source
71 .parse()
72 .map_err(|e: LexError| diag(e.span(), e.to_string()))?;
73 let mut cur = Cursor::new(tokens, None);
74 let mut points: Vec<PointType> = Vec::new();
75 while !cur.at_end() {
76 if cur.peek_word("point") {
77 read_declaration(&mut cur, &mut points)?;
78 } else {
79 skip_function(&mut cur)?;
80 }
81 }
82 Ok(points)
83}
84
85pub(crate) fn read_declaration(cur: &mut Cursor, points: &mut Vec<PointType>) -> Res<()> {
88 let point = parse_declaration(cur, points)?;
89 points.push(point);
90 Ok(())
91}
92
93fn skip_function(cur: &mut Cursor) -> Res<()> {
98 while cur.peek_punct('#') {
99 cur.bump();
100 if !matches!(cur.peek(), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket) {
101 return Err(cur.expected("`[`"));
102 }
103 cur.bump();
104 }
105 if !cur.peek_word("fn") {
106 return Err(cur.expected("`point` or `fn`"));
107 }
108 cur.bump();
109 if !matches!(cur.peek(), Some(TokenTree::Ident(_))) {
110 return Err(cur.expected("a function name"));
111 }
112 cur.bump();
113 expect_group(cur, Delimiter::Parenthesis, "`(`")?;
114 if !(cur.peek_punct('-') && is_punct(cur.peek_at(1), '>')) {
115 return Err(cur.expected("`->`"));
116 }
117 cur.bump();
118 cur.bump();
119 if !cur.peek_word("Sign") {
120 return Err(cur.expected("`Sign`"));
121 }
122 cur.bump();
123 expect_group(cur, Delimiter::Brace, "`{`")
124}
125
126fn expect_group(cur: &mut Cursor, delimiter: Delimiter, what: &str) -> Res<()> {
128 if !matches!(cur.peek(), Some(TokenTree::Group(g)) if g.delimiter() == delimiter) {
129 return Err(cur.expected(what));
130 }
131 cur.bump();
132 Ok(())
133}
134
135fn parse_declaration(cur: &mut Cursor, seen: &[PointType]) -> Res<PointType> {
136 cur.bump();
137 let (name, name_span) = parse_name(cur, "a point name", "a point")?;
138 if seen.iter().any(|p| p.name == name) {
139 return Err(diag(name_span, format!("`{name}` is already defined")));
140 }
141 let params = parse_params(cur)?;
142 if !cur.peek_punct('=') {
143 return Err(cur.expected("`=`"));
144 }
145 cur.bump();
146 let start = cur.peek().map_or_else(|| cur.close(), TokenTree::span);
147 let mut scope = Scope {
148 builder: PointBuilder::default(),
149 params: ¶ms,
150 env: params
151 .iter()
152 .enumerate()
153 .map(|(i, p)| (p.name.as_str(), i))
154 .collect(),
155 };
156 let value = parse_expr(cur, &mut scope, 0)?;
157 let Value::Vector(roots) = value else {
158 return Err(diag(
159 start,
160 "the value of a point must be an `F64x3`, not an `f64`",
161 ));
162 };
163 let constraints = if cur.peek_word("where") {
164 cur.bump();
165 parse_constraints(cur, &scope)?
166 } else {
167 Vec::new()
168 };
169 if !cur.peek_punct(';') {
170 return Err(cur.expected("an operator, `where` or `;`"));
171 }
172 cur.bump();
173 let graph = scope.builder.finish(roots);
174 if let Some(param) = params
175 .iter()
176 .enumerate()
177 .find_map(|(i, p)| (!is_used(&graph, i)).then_some(p))
178 {
179 return Err(Diag {
180 line: param.position.line,
181 column: param.position.column,
182 message: format!(
183 "unused parameter `{}`: the expression does not use it",
184 param.name
185 ),
186 });
187 }
188 Ok(PointType {
189 name,
190 position: Position::of(name_span),
191 params,
192 graph,
193 constraints,
194 })
195}
196
197fn is_used(graph: &PointGraph, index: usize) -> bool {
199 graph.nodes.iter().any(|n| {
200 matches!(n, PointNode::Coord { param, .. } | PointNode::Scalar(param) if *param == index)
201 })
202}
203
204fn parse_name(cur: &mut Cursor, what: &str, of: &str) -> Res<(String, Span)> {
207 let Some(TokenTree::Ident(id)) = cur.peek() else {
208 return Err(cur.expected(what));
209 };
210 let (name, span) = (id.to_string(), id.span());
211 if !can_name_a_module(&name) {
212 return Err(diag(
213 span,
214 format!(
215 "`{name}` cannot be used as the name of {of}: \
216 it is a Rust keyword or a raw identifier"
217 ),
218 ));
219 }
220 cur.bump();
221 Ok((name, span))
222}
223
224fn parse_params(cur: &mut Cursor) -> Res<Vec<PointParam>> {
226 let (stream, close) = match cur.peek() {
227 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
228 (g.stream(), g.span_close())
229 }
230 _ => return Err(cur.expected("`(`")),
231 };
232 cur.bump();
233 let mut inside = Cursor::new(stream, Some(close));
234 let mut params: Vec<PointParam> = Vec::new();
235 while !inside.at_end() {
236 let (name, span) = parse_name(&mut inside, "a parameter name", "a parameter")?;
237 if params.iter().any(|p| p.name == name) {
238 return Err(diag(span, format!("`{name}` redefines a name")));
239 }
240 if !inside.peek_punct(':') {
241 return Err(inside.expected("`:`"));
242 }
243 inside.bump();
244 let ty = match inside.peek() {
245 Some(TokenTree::Ident(id)) if id == "F64x3" => ParamType::F64x3,
246 Some(TokenTree::Ident(id)) if id == "f64" => ParamType::F64,
247 Some(token) => {
248 return Err(diag(
249 token.span(),
250 "a parameter must have type `F64x3` or `f64`",
251 ));
252 }
253 None => return Err(inside.expected("a type: `F64x3` or `f64`")),
254 };
255 inside.bump();
256 params.push(PointParam {
257 name,
258 ty,
259 position: Position::of(span),
260 });
261 if !inside.at_end() {
262 if !inside.peek_punct(',') {
263 return Err(inside.expected("`,`"));
264 }
265 inside.bump();
266 }
267 }
268 Ok(params)
269}
270
271fn parse_constraints(cur: &mut Cursor, scope: &Scope) -> Res<Vec<Constraint>> {
273 let mut constraints = Vec::new();
274 loop {
275 let start = operator_span(cur);
276 let lhs = parse_sum(cur, scope)?;
277 let is_le = cur.peek_punct('<')
278 && matches!(cur.peek(), Some(TokenTree::Punct(p)) if p.spacing() == proc_macro2::Spacing::Joint)
279 && cur
280 .peek_at(1)
281 .is_some_and(|t| crate::cursor::is_punct(Some(t), '='));
282 if !is_le {
283 return Err(cur.expected("`<=`: a `where` clause only has `<=` comparisons"));
284 }
285 cur.bump();
286 cur.bump();
287 let rhs = parse_sum(cur, scope)?;
288 if lhs.params.is_empty() && rhs.params.is_empty() {
289 return Err(diag(
290 start,
291 "this comparison names no parameter, so it constrains nothing: \
292 a `where` clause compares `f64` parameters",
293 ));
294 }
295 constraints.push(Constraint {
296 lhs,
297 rhs,
298 position: Position::of(start),
299 });
300 if !cur.peek_punct(',') {
301 return Ok(constraints);
302 }
303 cur.bump();
304 }
305}
306
307fn parse_sum(cur: &mut Cursor, scope: &Scope) -> Res<LinearSum> {
309 let mut sum = LinearSum {
310 params: Vec::new(),
311 constant: 0,
312 };
313 loop {
314 match cur.peek() {
315 Some(TokenTree::Ident(id)) => {
316 let (name, span) = (id.to_string(), id.span());
317 match scope.env.get(name.as_str()) {
318 None => return Err(diag(span, format!("unknown name `{name}`"))),
319 Some(&i) if scope.params[i].ty == ParamType::F64x3 => {
320 return Err(diag(
321 span,
322 format!(
323 "`{name}` is an `F64x3`: a `where` clause only uses `f64` parameters"
324 ),
325 ));
326 }
327 Some(&i) => sum.params.push(i),
328 }
329 }
330 Some(TokenTree::Literal(lit)) => {
331 let span = lit.span();
332 let value = int_literal(&lit.to_string(), span)?;
333 sum.constant = sum.constant.checked_add(value).ok_or_else(|| {
334 diag(span, "the sum of the integers does not fit in an `i64`")
335 })?;
336 }
337 _ => return Err(cur.expected("an `f64` parameter or an integer")),
338 }
339 cur.bump();
340 if cur.peek_punct('-') || cur.peek_punct('*') {
341 return Err(diag(
342 operator_span(cur),
343 "only sums are supported in a `where` clause",
344 ));
345 }
346 if !cur.peek_punct('+') {
347 return Ok(sum);
348 }
349 cur.bump();
350 }
351}