1use std::collections::{BTreeSet, HashMap};
16use std::sync::{Arc, Mutex};
17
18use crate::ast::PortType;
19use crate::iteration::comprehension::source::{LiteralValue, Source};
20use crate::iteration::comprehension::{Comprehension, StreamerValue};
21use crate::kernel::PolydatProgram;
22
23use super::ast::{
24 Arg, Binding, BindingModifier, CallExpr, Expr, ExternPort, ForSource, ForSourceKind, ForStmt,
25 InputDecl, PolydatFile, Statement,
26};
27use super::lexer::Span;
28
29#[derive(Debug, Clone)]
32pub struct Traversal {
33 pub span: Span,
35 pub source_text: String,
37 pub comprehension: Comprehension,
40 pub elements: Vec<(String, PortType)>,
42 pub cascade: Vec<(String, PortType)>,
45 pub program: Arc<PolydatProgram>,
48 pub body: Arc<BodySource>,
52}
53
54pub struct BodySource {
59 pub(crate) file: PolydatFile,
60 pub(crate) source_text: String,
61 pub(crate) source_dir: Option<std::path::PathBuf>,
62 pub(crate) lib_paths: Vec<std::path::PathBuf>,
63 pub(crate) strict: bool,
64 pub(crate) context_label: String,
65 pub(crate) cursor_limit: Option<u64>,
66 pub(crate) pragmas: super::pragmas::PragmaSet,
67 pub(super) modules: HashMap<String, super::modules::ResolvedModule>,
71 pub(crate) programs: Mutex<HashMap<crate::Engine, Arc<dyn crate::kernel::KernelProgram>>>,
73 pub(crate) ledger: Arc<crate::kernel::CompileLedger>,
76}
77
78impl BodySource {
79 pub fn source_text(&self) -> &str {
83 &self.source_text
84 }
85}
86
87impl std::fmt::Debug for BodySource {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("BodySource")
90 .field("context", &self.context_label)
91 .field("statements", &self.file.statements.len())
92 .finish()
93 }
94}
95
96impl Traversal {
97 pub fn program_on(
102 &self,
103 engine: crate::Engine,
104 ) -> Result<Arc<dyn crate::kernel::KernelProgram>, crate::KernelError> {
105 if matches!(engine, crate::Engine::Interpreter(_)) {
106 return Ok(self.program.clone());
107 }
108 let mut programs = self
109 .body
110 .programs
111 .lock()
112 .unwrap_or_else(|poisoned| poisoned.into_inner());
113 if let Some(program) = programs.get(&engine) {
114 return Ok(program.clone());
115 }
116 let program = super::compile::Compiler::compile_body_on(&self.body, engine)?.into_program();
117 programs.insert(engine, program.clone());
118 Ok(program)
119 }
120}
121
122#[derive(Debug, Clone)]
125pub struct Producer {
126 pub name: String,
128 pub span: Span,
130 pub source_text: String,
132 pub comprehension: Comprehension,
134}
135
136pub fn strip_for_forms(
145 file: &PolydatFile,
146) -> Result<(PolydatFile, Vec<ForStmt>, Vec<Producer>), String> {
147 let mut parent = Vec::with_capacity(file.statements.len());
148 let mut fors = Vec::new();
149 let mut producers: Vec<Producer> = Vec::new();
150 for stmt in &file.statements {
151 match stmt {
152 Statement::For(f) => fors.push(f.clone()),
153 Statement::Binding(b) if matches!(b.value, Expr::For(_)) => {
154 let Expr::For(source) = &b.value else {
155 unreachable!()
156 };
157 let comprehension = resolve_source(source, &producers)?;
158 let name = b.targets.join(",");
159 let value = StreamerValue::new(source.text.clone(), comprehension.clone());
160 parent.push(Statement::Binding(Binding {
161 targets: b.targets.clone(),
162 value: Expr::Call(CallExpr {
163 func: "streamer".into(),
164 args: vec![Arg::Positional(Expr::StringLit(value.to_json(), b.span))],
165 span: b.span,
166 }),
167 modifier: BindingModifier::CONST,
168 type_annotation: None,
169 span: b.span,
170 }));
171 producers.push(Producer {
172 name,
173 span: b.span,
174 source_text: source.text.clone(),
175 comprehension,
176 });
177 }
178 other => parent.push(other.clone()),
179 }
180 }
181 Ok((PolydatFile { statements: parent }, fors, producers))
182}
183
184pub fn resolve_source(source: &ForSource, producers: &[Producer]) -> Result<Comprehension, String> {
188 let find = |name: &str| -> Result<Comprehension, String> {
189 producers
190 .iter()
191 .rev()
192 .find(|p| p.name == name)
193 .map(|p| p.comprehension.clone())
194 .ok_or_else(|| {
195 let known: Vec<&str> = producers.iter().map(|p| p.name.as_str()).collect();
196 format!(
197 "`for {}` at line {}, col {}: no producer named '{name}' is bound in this scope{}",
198 source.text,
199 source.span.line,
200 source.span.col,
201 if known.is_empty() { String::new() } else { format!("; producers here: {}", known.join(", ")) }
202 )
203 })
204 };
205 match &source.kind {
206 ForSourceKind::Comprehension(c) => Ok(c.clone()),
207 ForSourceKind::Producer(name) => find(name),
208 ForSourceKind::Derived {
209 base,
210 filter,
211 order,
212 } => {
213 let mut c = find(base)?;
214 if let Some(pred) = filter {
215 c = Comprehension::filter(c, pred.clone());
216 }
217 if let Some(spec) = order {
218 let (strategy, truncation) = parse_order(spec).map_err(|e| {
219 format!(
220 "`for {}` at line {}, col {}: {e}",
221 source.text, source.span.line, source.span.col
222 )
223 })?;
224 c = Comprehension::order(c, strategy, truncation);
225 }
226 Ok(c)
227 }
228 }
229}
230
231fn parse_order(
235 spec: &str,
236) -> Result<(crate::iteration::comprehension::StrategyName, Option<u64>), String> {
237 let carrier = format!("__o in 0..1 order {spec}");
238 let legacy = crate::iteration::comprehension::parse::parse_comprehension_text(&carrier)?;
239 let algebra = crate::iteration::comprehension::spec::legacy_to_algebra(&legacy)
240 .map_err(|e| e.to_string())?;
241 match algebra {
242 Comprehension::Order {
243 strategy,
244 truncation,
245 ..
246 } => Ok((strategy, truncation)),
247 other => Err(format!(
248 "order spec `{spec}` did not produce an ordering (got {other:?})"
249 )),
250 }
251}
252
253pub fn element_types(
257 comprehension: &Comprehension,
258 probe: &mut dyn FnMut(&str) -> Result<PortType, String>,
259) -> Result<Vec<(String, PortType)>, String> {
260 let mut out = Vec::new();
261 collect_element_types(comprehension, probe, &mut out)?;
262 Ok(out)
263}
264
265fn collect_element_types(
266 c: &Comprehension,
267 probe: &mut dyn FnMut(&str) -> Result<PortType, String>,
268 out: &mut Vec<(String, PortType)>,
269) -> Result<(), String> {
270 match c {
271 Comprehension::Clause { name, source } => {
272 if out.iter().any(|(n, _)| n == name) {
273 return Ok(());
274 }
275 let ty = source_type(name, source, probe)?;
276 out.push((name.clone(), ty));
277 Ok(())
278 }
279 Comprehension::Cartesian { children } | Comprehension::Zip { children, .. } => {
280 for child in children {
281 collect_element_types(child, probe, out)?;
282 }
283 Ok(())
284 }
285 Comprehension::Union { children } => {
286 if let Some(first) = children.first() {
289 collect_element_types(first, probe, out)?;
290 }
291 Ok(())
292 }
293 Comprehension::Filter { child, .. } | Comprehension::Order { child, .. } => {
294 collect_element_types(child, probe, out)
295 }
296 }
297}
298
299fn source_type(
300 name: &str,
301 source: &Source,
302 probe: &mut dyn FnMut(&str) -> Result<PortType, String>,
303) -> Result<PortType, String> {
304 match source {
305 Source::Literal { values } => {
306 let mut ty: Option<PortType> = None;
307 for v in values {
308 let t = match v {
309 LiteralValue::Int(_) => PortType::U64,
310 LiteralValue::Float(_) => PortType::F64,
311 LiteralValue::String(_) => PortType::Str,
312 LiteralValue::Bool(_) => PortType::Bool,
313 LiteralValue::Json(_) => PortType::Json,
314 };
315 match ty {
316 None => ty = Some(t),
317 Some(PortType::F64) if t == PortType::U64 => {}
319 Some(PortType::U64) if t == PortType::F64 => ty = Some(PortType::F64),
320 Some(prev) if prev != t => {
321 return Err(format!(
322 "element '{name}': literal list mixes {prev:?} and {t:?} values; a comprehension element has one type"
323 ));
324 }
325 Some(_) => {}
326 }
327 }
328 ty.ok_or_else(|| format!("element '{name}': literal list is empty"))
329 }
330 Source::IntRange { .. } => Ok(PortType::U64),
331 Source::ContinuousInterval { .. } | Source::Distribution { .. } => Ok(PortType::F64),
332 Source::WorkloadParamList { .. } => Ok(PortType::Str),
334 Source::Generator { expr, .. } => {
335 let head = expr.trim();
336 if head.starts_with("partitions(")
337 || head.starts_with("subdivide(")
338 || head.ends_with(".partitions")
339 {
340 return Ok(PortType::Ext);
341 }
342 probe(head)
343 .map_err(|e| format!("element '{name}': cannot type generator `{head}`: {e}"))
344 }
345 }
346}
347
348fn body_declared(body: &[Statement], elements: &[(String, PortType)]) -> BTreeSet<String> {
352 let mut names: BTreeSet<String> = elements.iter().map(|(n, _)| n.clone()).collect();
353 names.insert("cycle".to_string());
354 for stmt in body {
355 match stmt {
356 Statement::Binding(b) => names.extend(b.targets.iter().cloned()),
357 Statement::InputDecl(d) => {
358 names.insert(d.name.clone());
359 }
360 Statement::ExternPort(p) => {
361 names.insert(p.name.clone());
362 }
363 Statement::ModuleDef(m) => {
364 names.insert(m.name.clone());
365 }
366 Statement::Cursor(c) => {
367 names.insert(c.name.clone());
368 }
369 Statement::Pragma { .. } => {}
370 Statement::For(f) => {
371 let _ = f;
374 }
375 Statement::Tile(t) => {
376 names.insert(t.name.clone());
377 }
378 }
379 }
380 names
381}
382
383fn tile_references(pieces: &[super::ast::TilePiece], out: &mut BTreeSet<String>) {
385 use super::ast::TilePiece;
386 use super::refs::collect_expr_refs;
387 for piece in pieces {
388 match piece {
389 TilePiece::Static(_) => {}
390 TilePiece::Hole(h) => collect_expr_refs(&h.expr, out),
391 TilePiece::Projection { body, .. } => tile_references(body, out),
392 TilePiece::Branch {
393 cond,
394 then,
395 otherwise,
396 ..
397 } => {
398 collect_expr_refs(cond, out);
399 tile_references(then, out);
400 if let Some(o) = otherwise {
401 tile_references(o, out);
402 }
403 }
404 }
405 }
406}
407
408fn body_references(body: &[Statement], out: &mut BTreeSet<String>) {
413 use super::refs::collect_expr_refs;
414 for stmt in body {
415 match stmt {
416 Statement::Binding(b) => collect_expr_refs(&b.value, out),
417 Statement::ExternPort(p) => {
418 if let Some(d) = &p.default {
419 collect_expr_refs(d, out);
420 }
421 }
422 Statement::Cursor(c) => {
423 collect_expr_refs(&c.constructor, out);
424 if let Some(over) = &c.over {
425 collect_expr_refs(over, out);
426 }
427 }
428 Statement::For(f) => {
429 let mut inner = BTreeSet::new();
430 body_references(&f.body, &mut inner);
431 let own = body_declared(&f.body, &[]);
432 let elems: BTreeSet<String> = f.source.element_names().into_iter().collect();
433 for n in inner {
434 if !own.contains(&n) && !elems.contains(&n) {
435 out.insert(n);
436 }
437 }
438 }
439 Statement::Tile(t) => tile_references(&t.pieces, out),
440 Statement::InputDecl(_) | Statement::ModuleDef(_) | Statement::Pragma { .. } => {}
441 }
442 }
443}
444
445pub fn child_file(
450 f: &ForStmt,
451 comprehension: &Comprehension,
452 elements: &[(String, PortType)],
453 type_of: &dyn Fn(&str) -> Option<PortType>,
454) -> Result<(PolydatFile, Vec<(String, PortType)>), String> {
455 for stmt in &f.body {
457 if let Statement::InputDecl(d) = stmt
458 && d.name != "cycle"
459 {
460 return Err(format!(
461 "`for {}` at line {}, col {}: a traversal body cannot declare input '{}'; only `cycle` is a coordinate inside a body, and the comprehension supplies the rest",
462 f.source.text, f.span.line, f.span.col, d.name
463 ));
464 }
465 }
466 let declared = body_declared(&f.body, elements);
467 let mut referenced = BTreeSet::new();
468 body_references(&f.body, &mut referenced);
469 referenced.extend(comprehension.referenced_source_names());
472
473 let mut cascade = Vec::new();
474 for name in referenced {
475 if declared.contains(&name) {
476 continue;
477 }
478 let ty = type_of(&name);
479 if let Some(ty) = ty {
480 cascade.push((name, ty));
481 }
482 }
485
486 let span = f.span;
487 let mut statements = Vec::with_capacity(f.body.len() + elements.len() + cascade.len() + 1);
488 if !f.body.iter().any(|s| matches!(s, Statement::InputDecl(_))) {
489 statements.push(Statement::InputDecl(InputDecl {
490 name: "cycle".into(),
491 ty: Some("u64".into()),
492 span,
493 }));
494 }
495 for (name, ty) in elements {
496 statements.push(Statement::ExternPort(ExternPort {
497 name: name.clone(),
498 typ: ty.to_keyword().to_string(),
499 default: None,
500 span,
501 }));
502 }
503 for (name, ty) in &cascade {
504 statements.push(Statement::ExternPort(ExternPort {
505 name: name.clone(),
506 typ: ty.to_keyword().to_string(),
507 default: None,
508 span,
509 }));
510 }
511 statements.extend(f.body.iter().cloned());
512 Ok((PolydatFile { statements }, cascade))
513}