polydat_grammar/comprehension/spec/
serde_form.rs1use serde::{Deserialize, Serialize};
26
27use crate::comprehension::ast::Comprehension as AlgebraAst;
28use crate::comprehension::ast_legacy::{Clause as LegacyClause, Comprehension as LegacyAst};
29use crate::comprehension::parse::{
30 comprehension_from_subspaces, parse_clause_list, parse_order_spec,
31};
32
33use super::legacy_convert::{ConvertError, legacy_to_algebra};
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct ComprehensionSpec {
43 #[serde(rename = "for")]
47 pub r#for: ForSpec,
48 #[serde(default, rename = "where", skip_serializing_if = "Option::is_none")]
52 pub r#where: Option<String>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
58 pub order: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
73#[serde(untagged)]
74pub enum ForSpec {
75 Inline(String),
78 ClauseList(Vec<String>),
81 UnionOfClauseLists(Vec<Vec<String>>),
84}
85
86#[derive(Debug, Clone)]
89pub enum SpecConvertError {
90 ParseClause {
92 input: String,
94 message: String,
96 },
97 ParseOrder {
99 input: String,
101 message: String,
103 },
104 LegacyValidate {
106 errors: Vec<String>,
108 },
109 Convert(ConvertError),
111}
112
113impl std::fmt::Display for SpecConvertError {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 match self {
116 SpecConvertError::ParseClause { input, message } => {
117 write!(f, "failed to parse clause(s) {input:?}: {message}")
118 }
119 SpecConvertError::ParseOrder { input, message } => {
120 write!(f, "failed to parse order {input:?}: {message}")
121 }
122 SpecConvertError::LegacyValidate { errors } => {
123 write!(f, "legacy AST validation failed: {}", errors.join("; "))
124 }
125 SpecConvertError::Convert(e) => {
126 write!(f, "algebra conversion failed: {e}")
127 }
128 }
129 }
130}
131
132impl std::error::Error for SpecConvertError {}
133
134impl From<ConvertError> for SpecConvertError {
135 fn from(e: ConvertError) -> Self {
136 SpecConvertError::Convert(e)
137 }
138}
139
140pub fn parse_inline(spec: &str) -> Result<AlgebraAst, SpecConvertError> {
150 ComprehensionSpec {
151 r#for: ForSpec::Inline(spec.to_string()),
152 r#where: None,
153 order: None,
154 }
155 .into_algebra()
156}
157
158impl ComprehensionSpec {
159 pub fn into_algebra(self) -> Result<AlgebraAst, SpecConvertError> {
165 let legacy = self.into_legacy()?;
166 let algebra = legacy_to_algebra(&legacy)?;
167 Ok(algebra)
168 }
169
170 pub fn into_legacy(self) -> Result<LegacyAst, SpecConvertError> {
174 let subspaces = self.r#for.into_subspaces()?;
175 let mut legacy = comprehension_from_subspaces(subspaces);
176 if let Some(predicate) = self.r#where {
177 legacy = legacy.with_filter(predicate);
178 }
179 if let Some(order_text) = self.order {
180 let order =
181 parse_order_spec(&order_text).map_err(|msg| SpecConvertError::ParseOrder {
182 input: order_text.clone(),
183 message: msg,
184 })?;
185 legacy = legacy.with_order(order);
186 }
187 legacy
188 .validate()
189 .map_err(|errs| SpecConvertError::LegacyValidate { errors: errs })?;
190 Ok(legacy)
191 }
192}
193
194impl ForSpec {
195 fn into_subspaces(self) -> Result<Vec<Vec<LegacyClause>>, SpecConvertError> {
198 match self {
199 ForSpec::Inline(text) => {
200 let clauses =
205 parse_clause_list(&text).map_err(|message| SpecConvertError::ParseClause {
206 input: text.clone(),
207 message,
208 })?;
209 Ok(clauses.into_iter().map(|c| vec![c]).collect())
210 }
211 ForSpec::ClauseList(entries) => {
212 let mut subspaces = Vec::with_capacity(entries.len());
215 for entry in entries {
216 let clauses = parse_clause_list(&entry).map_err(|message| {
217 SpecConvertError::ParseClause {
218 input: entry.clone(),
219 message,
220 }
221 })?;
222 for c in clauses {
223 subspaces.push(vec![c]);
224 }
225 }
226 Ok(subspaces)
227 }
228 ForSpec::UnionOfClauseLists(groups) => {
229 let mut subspaces = Vec::with_capacity(groups.len());
232 for group in groups {
233 let mut subspace_clauses = Vec::with_capacity(group.len());
234 for entry in group {
235 let clauses = parse_clause_list(&entry).map_err(|message| {
236 SpecConvertError::ParseClause {
237 input: entry.clone(),
238 message,
239 }
240 })?;
241 subspace_clauses.extend(clauses);
242 }
243 subspaces.push(subspace_clauses);
244 }
245 Ok(subspaces)
246 }
247 }
248 }
249}
250
251#[cfg(test)]
252mod tests {
253 use super::*;
254 use crate::comprehension::strategy::StrategyName;
255
256 #[test]
257 fn inline_single_clause() {
258 let yaml = r#"
259 for: "k in 1..10"
260 "#;
261 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
262 let algebra = spec.into_algebra().unwrap();
263 assert!(matches!(algebra, AlgebraAst::Clause { .. }));
265 }
266
267 #[test]
268 fn inline_multi_clause_cartesian() {
269 let yaml = r#"
270 for: "k in 1..10, limit in [10, 100, 1000]"
271 "#;
272 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
273 let algebra = spec.into_algebra().unwrap();
274 match algebra {
275 AlgebraAst::Cartesian { children } => assert_eq!(children.len(), 2),
276 other => panic!("expected Cartesian, got {other:?}"),
277 }
278 }
279
280 #[test]
281 fn clause_list_form_cartesian() {
282 let yaml = r#"
283 for:
284 - "k in 1..10"
285 - "limit in [10, 100, 1000]"
286 "#;
287 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
288 let algebra = spec.into_algebra().unwrap();
289 match algebra {
290 AlgebraAst::Cartesian { children } => assert_eq!(children.len(), 2),
291 other => panic!("expected Cartesian, got {other:?}"),
292 }
293 }
294
295 #[test]
296 fn union_of_clause_lists() {
297 let yaml = r#"
298 for:
299 - ["k in 10", "limit in [1, 2, 3]"]
300 - ["k in 100", "limit in [10, 20, 30]"]
301 "#;
302 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
303 let algebra = spec.into_algebra().unwrap();
304 match algebra {
305 AlgebraAst::Union { children } => assert_eq!(children.len(), 2),
306 other => panic!("expected Union, got {other:?}"),
307 }
308 }
309
310 #[test]
311 fn where_clause_wraps_with_filter() {
312 let yaml = r#"
313 for: "k in 1..10"
314 where: "{k} > 5"
315 "#;
316 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
317 let algebra = spec.into_algebra().unwrap();
318 assert!(matches!(algebra, AlgebraAst::Filter { .. }));
319 }
320
321 #[test]
322 fn order_clause_wraps_with_order() {
323 let yaml = r#"
324 for: "k in 1..10, limit in 1..100"
325 order: "halton/50"
326 "#;
327 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
328 let algebra = spec.into_algebra().unwrap();
329 match algebra {
330 AlgebraAst::Order {
331 strategy: StrategyName::Halton,
332 truncation: Some(50),
333 ..
334 } => {}
335 other => panic!("expected Order(Halton, Some(50)), got {other:?}"),
336 }
337 }
338
339 #[test]
340 fn where_and_order_compose() {
341 let yaml = r#"
342 for: "k in 1..10, limit in 1..100"
343 where: "{k} * {limit} <= 100"
344 order: "lex/20"
345 "#;
346 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
347 let algebra = spec.into_algebra().unwrap();
348 match algebra {
350 AlgebraAst::Order {
351 child,
352 strategy: StrategyName::Lex,
353 truncation: Some(20),
354 ..
355 } => {
356 assert!(matches!(*child, AlgebraAst::Filter { .. }));
357 }
358 other => panic!("expected Order(Lex, Some(20)) wrapping Filter, got {other:?}"),
359 }
360 }
361
362 #[test]
363 fn json_input_round_trips() {
364 let json = r#"
365 {
366 "for": ["k in 1..10", "limit in [10, 100]"],
367 "where": "{k} > 0",
368 "order": "halton/20"
369 }
370 "#;
371 let spec: ComprehensionSpec = serde_json::from_str(json).unwrap();
372 let algebra = spec.into_algebra().unwrap();
373 assert!(matches!(algebra, AlgebraAst::Order { .. }));
374 }
375
376 #[test]
377 fn malformed_clause_surfaces_error() {
378 let yaml = r#"
379 for: "this is not a valid clause"
380 "#;
381 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
382 let err = spec.into_algebra().unwrap_err();
383 assert!(matches!(err, SpecConvertError::ParseClause { .. }));
384 }
385
386 #[test]
387 fn malformed_order_surfaces_error() {
388 let yaml = r#"
389 for: "k in 1..10"
390 order: "(((not valid"
391 "#;
392 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
393 let err = spec.into_algebra().unwrap_err();
394 assert!(matches!(err, SpecConvertError::ParseOrder { .. }));
395 }
396
397 #[test]
398 fn unparseable_source_inside_for_falls_back_to_generator() {
399 let yaml = r#"
404 for: "k in something-weird"
405 "#;
406 let spec: ComprehensionSpec = serde_yaml::from_str(yaml).unwrap();
407 let algebra = spec.into_algebra().expect("permissive accept");
408 match algebra {
409 AlgebraAst::Clause { source, .. } => match source {
410 crate::comprehension::source::Source::Generator { expr, .. } => {
411 assert_eq!(expr, "something-weird");
412 }
413 other => panic!("expected Generator, got {other:?}"),
414 },
415 other => panic!("expected Clause, got {other:?}"),
416 }
417 }
418}