1use std::collections::{BTreeSet, HashMap};
16use std::sync::{Arc, Mutex};
17
18use crate::ast::PortType;
19use crate::iteration::comprehension::flatten::flatten_static_sources;
20use crate::iteration::comprehension::source::{LiteralValue, Source};
21use crate::iteration::comprehension::{
22 Comprehension, Mode as ValidationMode, StreamerValue, ValidationWarning,
23};
24use crate::kernel::PolydatProgram;
25use crate::kernel::interp::{Lookup, NoScope};
26
27use super::ast::{
28 Arg, Binding, BindingModifier, CallExpr, Expr, ExternPort, ForSource, ForSourceKind, ForStmt,
29 InputDecl, PolydatFile, Statement,
30};
31use super::lexer::Span;
32
33#[derive(Debug, Clone)]
36pub struct Traversal {
37 pub span: Span,
39 pub source_text: String,
41 pub comprehension: Comprehension,
44 pub elements: Vec<(String, PortType)>,
46 pub cascade: Vec<(String, PortType)>,
49 pub program: Arc<PolydatProgram>,
52 pub body: Arc<BodySource>,
56}
57
58pub struct BodySource {
63 pub(crate) file: PolydatFile,
64 pub(crate) source_text: String,
65 pub(crate) source_dir: Option<std::path::PathBuf>,
66 pub(crate) lib_paths: Vec<std::path::PathBuf>,
67 pub(crate) strict: bool,
68 pub(crate) context_label: String,
69 pub(crate) cursor_limit: Option<u64>,
70 pub(crate) pragmas: super::pragmas::PragmaSet,
71 pub(super) modules: HashMap<String, super::modules::ResolvedModule>,
75 pub(crate) programs: Mutex<HashMap<crate::Engine, Arc<dyn crate::kernel::KernelProgram>>>,
77 pub(crate) ledger: Arc<crate::kernel::CompileLedger>,
80}
81
82impl BodySource {
83 pub fn source_text(&self) -> &str {
87 &self.source_text
88 }
89
90 pub fn program_on(
101 &self,
102 engine: crate::Engine,
103 ) -> Result<Arc<dyn crate::kernel::KernelProgram>, crate::KernelError> {
104 let mut programs = self
105 .programs
106 .lock()
107 .unwrap_or_else(|poisoned| poisoned.into_inner());
108 if let Some(program) = programs.get(&engine) {
109 return Ok(program.clone());
110 }
111 let program = super::compile::Compiler::compile_body_on(self, engine)?.into_program();
112 programs.insert(engine, program.clone());
113 Ok(program)
114 }
115
116 #[allow(clippy::too_many_arguments)]
118 pub(super) fn from_parts(
119 file: PolydatFile,
120 source_text: String,
121 source_dir: Option<std::path::PathBuf>,
122 lib_paths: Vec<std::path::PathBuf>,
123 strict: bool,
124 context_label: String,
125 cursor_limit: Option<u64>,
126 pragmas: super::pragmas::PragmaSet,
127 modules: HashMap<String, super::modules::ResolvedModule>,
128 ledger: Arc<crate::kernel::CompileLedger>,
129 ) -> Self {
130 BodySource {
131 file,
132 source_text,
133 source_dir,
134 lib_paths,
135 strict,
136 context_label,
137 cursor_limit,
138 pragmas,
139 modules,
140 programs: Mutex::new(HashMap::new()),
141 ledger,
142 }
143 }
144
145 pub fn from_source(source: &str, context_label: &str) -> Result<Self, String> {
152 let tokens = super::lexer::lex(source)?;
153 let file = super::parser::parse(tokens)?;
154 Ok(BodySource {
155 file,
156 source_text: source.to_string(),
157 source_dir: None,
158 lib_paths: Vec::new(),
159 strict: false,
160 context_label: context_label.to_string(),
161 cursor_limit: None,
162 pragmas: super::pragmas::PragmaSet::default(),
163 modules: HashMap::new(),
164 programs: Mutex::new(HashMap::new()),
165 ledger: Arc::new(crate::kernel::CompileLedger::default()),
166 })
167 }
168}
169
170impl std::fmt::Debug for BodySource {
171 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172 f.debug_struct("BodySource")
173 .field("context", &self.context_label)
174 .field("statements", &self.file.statements.len())
175 .finish()
176 }
177}
178
179impl Traversal {
180 pub fn program_on(
185 &self,
186 engine: crate::Engine,
187 ) -> Result<Arc<dyn crate::kernel::KernelProgram>, crate::KernelError> {
188 if matches!(engine, crate::Engine::Interpreter(_)) {
189 return Ok(self.program.clone());
190 }
191 self.body.program_on(engine)
192 }
193}
194
195#[derive(Debug, Clone)]
198pub struct Producer {
199 pub name: String,
201 pub span: Span,
203 pub source_text: String,
205 pub comprehension: Comprehension,
207}
208
209pub fn strip_for_forms(
218 file: &PolydatFile,
219 mode: ValidationMode,
220 scope: &dyn Lookup,
221 events: &mut Vec<super::events::CompileEvent>,
222) -> Result<(PolydatFile, Vec<ForStmt>, Vec<Producer>), String> {
223 let mut parent = Vec::with_capacity(file.statements.len());
224 let mut fors = Vec::new();
225 let mut producers: Vec<Producer> = Vec::new();
226 for stmt in &file.statements {
227 match stmt {
228 Statement::For(f) => fors.push(f.clone()),
229 Statement::Binding(b) if matches!(b.value, Expr::For(_)) => {
230 let Expr::For(source) = &b.value else {
231 unreachable!()
232 };
233 let (comprehension, warnings) =
234 resolve_source_with(source, &producers, mode, scope)?;
235 events.extend(warning_events(source, &warnings));
236 let name = b.targets.join(",");
237 let value = StreamerValue::new(source.to_text(), comprehension.clone());
238 parent.push(Statement::Binding(Binding {
239 targets: b.targets.clone(),
240 value: Expr::Call(CallExpr {
241 func: "streamer".into(),
242 args: vec![Arg::Positional(Expr::StringLit(value.to_json(), b.span))],
243 span: b.span,
244 }),
245 modifier: BindingModifier::CONST,
246 type_annotation: None,
247 span: b.span,
248 }));
249 producers.push(Producer {
250 name,
251 span: b.span,
252 source_text: source.to_text(),
253 comprehension,
254 });
255 }
256 other => parent.push(other.clone()),
257 }
258 }
259 Ok((PolydatFile { statements: parent }, fors, producers))
260}
261
262pub fn resolve_source(source: &ForSource, producers: &[Producer]) -> Result<Comprehension, String> {
266 resolve_source_with(
267 source,
268 producers,
269 ValidationMode::Permissive,
270 &NoScope::new(),
271 )
272 .map(|(c, _)| c)
273}
274
275pub(super) fn warning_events(
278 source: &ForSource,
279 warnings: &[ValidationWarning],
280) -> Vec<super::events::CompileEvent> {
281 warnings
282 .iter()
283 .map(|w| super::events::CompileEvent::ComprehensionWarning {
284 source: source.to_text(),
285 line: source.span.line,
286 col: source.span.col,
287 warning: w.to_string(),
288 })
289 .collect()
290}
291
292pub fn resolve_source_with(
296 source: &ForSource,
297 producers: &[Producer],
298 mode: ValidationMode,
299 scope: &dyn Lookup,
300) -> Result<(Comprehension, Vec<ValidationWarning>), String> {
301 let find = |name: &str| -> Result<Comprehension, String> {
302 producers
303 .iter()
304 .rev()
305 .find(|p| p.name == name)
306 .map(|p| p.comprehension.clone())
307 .ok_or_else(|| {
308 let known: Vec<&str> = producers.iter().map(|p| p.name.as_str()).collect();
309 format!(
310 "`for {}` at line {}, col {}: no producer named '{name}' is bound in this scope{}",
311 source.to_text(),
312 source.span.line,
313 source.span.col,
314 if known.is_empty() { String::new() } else { format!("; producers here: {}", known.join(", ")) }
315 )
316 })
317 };
318 let at = |e: &dyn std::fmt::Display| {
319 format!(
320 "`for {}` at line {}, col {}: {e}",
321 source.to_text(),
322 source.span.line,
323 source.span.col
324 )
325 };
326 let comprehension = match &source.kind {
327 ForSourceKind::Comprehension(c) => {
328 let written = source.to_text();
336 let from_text =
337 crate::iteration::comprehension::spec::parse_comprehension_algebra(&written)
338 .map_err(|e| at(&e))?;
339 if from_text != *c {
340 return Err(at(&format!(
341 "this comprehension does not survive being written and read back: it writes \
342 as `{written}`, which reads as a different comprehension. That is an \
343 inconsistency between the comprehension renderer and parser, not an error \
344 in this program"
345 )));
346 }
347 c.clone()
348 }
349 ForSourceKind::Producer(name) => find(name)?,
350 ForSourceKind::Derived {
351 base,
352 filter,
353 order,
354 } => {
355 let mut c = find(base)?;
356 if let Some(pred) = filter {
357 c = Comprehension::filter(c, pred.clone());
358 }
359 if let Some(spec) = order {
360 let (strategy, truncation, seed) = parse_order(spec).map_err(|e| at(&e))?;
361 c = Comprehension::order_seeded(c, strategy, truncation, seed);
362 }
363 c
364 }
365 };
366 let comprehension = flatten_static_sources(&comprehension, scope);
370 let report =
374 crate::iteration::comprehension::validate(&comprehension, mode).map_err(|e| at(&e))?;
375 Ok((comprehension, report.warnings))
376}
377
378fn parse_order(
381 spec: &str,
382) -> Result<
383 (
384 crate::iteration::comprehension::StrategyName,
385 Option<u64>,
386 Option<u64>,
387 ),
388 String,
389> {
390 crate::iteration::comprehension::spec::parse_order(spec)
391}
392
393pub fn element_types(
397 comprehension: &Comprehension,
398 probe: &mut dyn FnMut(&str) -> Result<PortType, String>,
399) -> Result<Vec<(String, PortType)>, String> {
400 let mut out = Vec::new();
401 collect_element_types(comprehension, probe, &mut out)?;
402 Ok(out)
403}
404
405fn collect_element_types(
406 c: &Comprehension,
407 probe: &mut dyn FnMut(&str) -> Result<PortType, String>,
408 out: &mut Vec<(String, PortType)>,
409) -> Result<(), String> {
410 match c {
411 Comprehension::Clause { name, source } => {
412 if out.iter().any(|(n, _)| n == name) {
413 return Ok(());
414 }
415 let ty = source_type(name, source, probe)?;
416 out.push((name.clone(), ty));
417 Ok(())
418 }
419 Comprehension::Cartesian { children } | Comprehension::Zip { children, .. } => {
420 for child in children {
421 collect_element_types(child, probe, out)?;
422 }
423 Ok(())
424 }
425 Comprehension::Union { children } => {
426 if let Some(first) = children.first() {
429 collect_element_types(first, probe, out)?;
430 }
431 Ok(())
432 }
433 Comprehension::Filter { child, .. } | Comprehension::Order { child, .. } => {
434 collect_element_types(child, probe, out)
435 }
436 }
437}
438
439fn source_type(
440 name: &str,
441 source: &Source,
442 probe: &mut dyn FnMut(&str) -> Result<PortType, String>,
443) -> Result<PortType, String> {
444 match source {
445 Source::Literal { values } => {
446 let mut ty: Option<PortType> = None;
447 for v in values {
448 let t = match v {
449 LiteralValue::Int(_) => PortType::U64,
450 LiteralValue::Float(_) => PortType::F64,
451 LiteralValue::String(_) => PortType::Str,
452 LiteralValue::Bool(_) => PortType::Bool,
453 LiteralValue::Json(_) => PortType::Json,
454 };
455 match ty {
456 None => ty = Some(t),
457 Some(PortType::F64) if t == PortType::U64 => {}
459 Some(PortType::U64) if t == PortType::F64 => ty = Some(PortType::F64),
460 Some(prev) if prev != t => {
461 return Err(format!(
462 "element '{name}': literal list mixes {prev:?} and {t:?} values; a comprehension element has one type"
463 ));
464 }
465 Some(_) => {}
466 }
467 }
468 ty.ok_or_else(|| format!("element '{name}': literal list is empty"))
469 }
470 Source::IntRange { .. } => Ok(PortType::U64),
471 Source::ContinuousInterval { .. } | Source::Distribution { .. } => Ok(PortType::F64),
472 Source::WorkloadParamList { .. } => Ok(PortType::Str),
474 Source::Generator { expr, .. } => {
475 let head = expr.trim();
476 if head.starts_with("partitions(")
477 || head.starts_with("subdivide(")
478 || head.ends_with(".partitions")
479 {
480 return Ok(PortType::Ext);
481 }
482 probe(head)
483 .map_err(|e| format!("element '{name}': cannot type generator `{head}`: {e}"))
484 }
485 }
486}
487
488fn body_declared(body: &[Statement], elements: &[(String, PortType)]) -> BTreeSet<String> {
492 let mut names: BTreeSet<String> = elements.iter().map(|(n, _)| n.clone()).collect();
493 names.insert("cycle".to_string());
494 for stmt in body {
495 match stmt {
496 Statement::Binding(b) => names.extend(b.targets.iter().cloned()),
497 Statement::InputDecl(d) => {
498 names.insert(d.name.clone());
499 }
500 Statement::ExternPort(p) => {
501 names.insert(p.name.clone());
502 }
503 Statement::ModuleDef(m) => {
504 names.insert(m.name.clone());
505 }
506 Statement::Cursor(c) => {
507 names.insert(c.name.clone());
508 }
509 Statement::Pragma { .. } => {}
510 Statement::For(f) => {
511 let _ = f;
514 }
515 Statement::Tile(t) => {
516 names.insert(t.name.clone());
517 }
518 }
519 }
520 names
521}
522
523fn tile_references(pieces: &[super::ast::TilePiece], out: &mut BTreeSet<String>) {
525 use super::ast::TilePiece;
526 use super::refs::collect_expr_refs;
527 for piece in pieces {
528 match piece {
529 TilePiece::Static(_) => {}
530 TilePiece::Hole(h) => collect_expr_refs(&h.expr, out),
531 TilePiece::Projection { body, .. } => tile_references(body, out),
532 TilePiece::Branch {
533 cond,
534 then,
535 otherwise,
536 ..
537 } => {
538 collect_expr_refs(cond, out);
539 tile_references(then, out);
540 if let Some(o) = otherwise {
541 tile_references(o, out);
542 }
543 }
544 }
545 }
546}
547
548fn body_references(body: &[Statement], out: &mut BTreeSet<String>) {
553 use super::refs::collect_expr_refs;
554 for stmt in body {
555 match stmt {
556 Statement::Binding(b) => collect_expr_refs(&b.value, out),
557 Statement::ExternPort(p) => {
558 if let Some(d) = &p.default {
559 collect_expr_refs(d, out);
560 }
561 }
562 Statement::Cursor(c) => {
563 collect_expr_refs(&c.constructor, out);
564 if let Some(over) = &c.over {
565 collect_expr_refs(over, out);
566 }
567 }
568 Statement::For(f) => {
569 let mut inner = BTreeSet::new();
570 body_references(&f.body, &mut inner);
571 let own = body_declared(&f.body, &[]);
572 let elems: BTreeSet<String> = f.source.element_names().into_iter().collect();
573 for n in inner {
574 if !own.contains(&n) && !elems.contains(&n) {
575 out.insert(n);
576 }
577 }
578 }
579 Statement::Tile(t) => tile_references(&t.pieces, out),
580 Statement::InputDecl(_) | Statement::ModuleDef(_) | Statement::Pragma { .. } => {}
581 }
582 }
583}
584
585pub fn child_file(
590 f: &ForStmt,
591 comprehension: &Comprehension,
592 elements: &[(String, PortType)],
593 type_of: &dyn Fn(&str) -> Option<PortType>,
594) -> Result<(PolydatFile, Vec<(String, PortType)>), String> {
595 for stmt in &f.body {
597 if let Statement::InputDecl(d) = stmt
598 && d.name != "cycle"
599 {
600 return Err(format!(
601 "`for {}` at line {}, col {}: a traversal body cannot declare input '{}'; only `cycle` is a coordinate inside a body, and the comprehension supplies the rest",
602 f.source.to_text(),
603 f.span.line,
604 f.span.col,
605 d.name
606 ));
607 }
608 }
609 let declared = body_declared(&f.body, elements);
610 let mut referenced = BTreeSet::new();
611 body_references(&f.body, &mut referenced);
612 referenced.extend(comprehension.referenced_source_names());
615
616 let mut cascade = Vec::new();
617 for name in referenced {
618 if declared.contains(&name) {
619 continue;
620 }
621 let ty = type_of(&name);
622 if let Some(ty) = ty {
623 cascade.push((name, ty));
624 }
625 }
628
629 let span = f.span;
630 let mut statements = Vec::with_capacity(f.body.len() + elements.len() + cascade.len() + 1);
631 if !f.body.iter().any(|s| matches!(s, Statement::InputDecl(_))) {
632 statements.push(Statement::InputDecl(InputDecl {
633 name: "cycle".into(),
634 ty: Some("u64".into()),
635 span,
636 }));
637 }
638 for (name, ty) in elements {
639 statements.push(Statement::ExternPort(ExternPort {
640 name: name.clone(),
641 typ: ty.to_keyword().to_string(),
642 default: None,
643 span,
644 }));
645 }
646 for (name, ty) in &cascade {
647 statements.push(Statement::ExternPort(ExternPort {
648 name: name.clone(),
649 typ: ty.to_keyword().to_string(),
650 default: None,
651 span,
652 }));
653 }
654 statements.extend(f.body.iter().cloned());
655 Ok((PolydatFile { statements }, cascade))
656}