1use std::collections::HashMap;
19use std::sync::Arc;
20
21use serde::{Deserialize, Serialize};
22
23use crate::ast::SlotShape;
24use crate::ast::{PortType, Value, ValueRef};
25use crate::iteration::comprehension::StreamerValue;
26use crate::iteration::comprehension::runtime::{RuntimeTuple, evaluate_for_iteration};
27use crate::kernel::{Kernel, KernelProgram, PolydatKernel, PolydatProgram};
28use crate::library::support::float_text;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
32pub enum HolePosition {
33 Value,
35 InString,
37 Text,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct HoleEncoding {
46 pub encoding: String,
48 pub position: HolePosition,
50 pub ty: Option<String>,
52 pub format: Option<String>,
54 pub raw: bool,
56 pub cond: bool,
58}
59
60impl HoleEncoding {
61 pub fn to_spec(&self) -> String {
63 let pos = match self.position {
64 HolePosition::Value => "value",
65 HolePosition::InString => "string",
66 HolePosition::Text => "text",
67 };
68 let mut flags = String::new();
69 if self.raw {
70 flags.push('r');
71 }
72 if self.cond {
73 flags.push('c');
74 }
75 format!(
76 "{}|{}|{}|{}|{}",
77 self.encoding,
78 pos,
79 self.ty.as_deref().unwrap_or(""),
80 self.format.as_deref().unwrap_or(""),
81 flags
82 )
83 }
84
85 pub fn from_spec(spec: &str) -> Self {
87 let mut parts = spec.splitn(5, '|');
88 let encoding = parts.next().unwrap_or("text").to_string();
89 let position = match parts.next().unwrap_or("text") {
90 "value" => HolePosition::Value,
91 "string" => HolePosition::InString,
92 _ => HolePosition::Text,
93 };
94 let ty = parts.next().filter(|s| !s.is_empty()).map(str::to_string);
95 let format = parts.next().filter(|s| !s.is_empty()).map(str::to_string);
96 let flags = parts.next().unwrap_or("");
97 HoleEncoding {
98 encoding,
99 position,
100 ty,
101 format,
102 raw: flags.contains('r'),
103 cond: flags.contains('c'),
104 }
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110pub enum HoleSource {
111 Wire {
114 index: usize,
116 spec: String,
118 },
119 Child {
122 name: String,
124 spec: String,
126 },
127}
128
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
131pub enum TileOp {
132 Static(String),
134 Hole(HoleSource),
136 Repeat {
138 stream: String,
140 child: usize,
142 sep: String,
144 body: Vec<TileOp>,
146 #[serde(default)]
150 generators: Vec<(String, usize, String)>,
151 },
152 Branch {
154 cond: HoleSource,
156 then: Vec<TileOp>,
158 otherwise: Vec<TileOp>,
160 },
161}
162
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167pub struct ChildSpec {
168 pub source: String,
170 pub cascade: Vec<(String, usize, String)>,
172}
173
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
176pub struct TileSpec {
177 pub name: String,
179 pub encoding: String,
181 pub ops: Vec<TileOp>,
183 pub children: Vec<ChildSpec>,
185}
186
187impl TileSpec {
188 pub fn to_json(&self) -> String {
190 serde_json::to_string(self).expect("TileSpec serializes")
191 }
192}
193
194#[derive(Debug)]
199enum RtOp {
200 Copy(&'static str),
202 Hole(RtSource, HoleEncoding),
204 Repeat {
205 stream: Arc<StreamerValue>,
206 child: usize,
207 sep: &'static str,
208 body: Vec<RtOp>,
209 generators: Vec<(String, usize, String)>,
210 },
211 Branch {
212 cond: RtSource,
213 then: Vec<RtOp>,
214 otherwise: Vec<RtOp>,
215 },
216}
217
218#[derive(Debug)]
220enum RtSource {
221 Wire(usize),
222 Child(String, usize),
225}
226
227fn lower_source(source: &HoleSource) -> (RtSource, HoleEncoding) {
228 match source {
229 HoleSource::Wire { index, spec } => (RtSource::Wire(*index), HoleEncoding::from_spec(spec)),
230 HoleSource::Child { name, spec } => (
231 RtSource::Child(name.clone(), 0),
232 HoleEncoding::from_spec(spec),
233 ),
234 }
235}
236
237fn lower_ops(ops: &[TileOp]) -> Vec<RtOp> {
240 use crate::kernel::StaticInterner;
241 ops.iter()
242 .map(|op| match op {
243 TileOp::Static(s) => RtOp::Copy(StaticInterner::intern(s)),
244 TileOp::Hole(h) => {
245 let (source, enc) = lower_source(h);
246 RtOp::Hole(source, enc)
247 }
248 TileOp::Repeat {
249 stream,
250 child,
251 sep,
252 body,
253 generators,
254 } => RtOp::Repeat {
255 stream: Arc::new(StreamerValue::from_json(stream)),
256 child: *child,
257 sep: StaticInterner::intern(sep),
258 body: lower_ops(body),
259 generators: generators.clone(),
260 },
261 TileOp::Branch {
262 cond,
263 then,
264 otherwise,
265 } => RtOp::Branch {
266 cond: lower_source(cond).0,
267 then: lower_ops(then),
268 otherwise: lower_ops(otherwise),
269 },
270 })
271 .collect()
272}
273
274pub struct TileProgram {
276 pub spec: TileSpec,
278 ops: Vec<RtOp>,
280 pub bodies: Vec<Arc<crate::dsl::traversal::BodySource>>,
288 pub children: Vec<Arc<PolydatProgram>>,
292 canonicals: Vec<Arc<PolydatKernel>>,
295 memo: Vec<Option<Arc<[RuntimeTuple]>>>,
299}
300
301impl std::fmt::Debug for TileProgram {
302 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
303 f.debug_struct("TileProgram")
304 .field("spec", &self.spec)
305 .field("ops", &self.ops)
306 .field("children", &self.children.len())
307 .finish_non_exhaustive()
308 }
309}
310
311fn number_child_holes(ops: &mut [RtOp]) {
315 fn walk(ops: &mut [RtOp], next: &mut usize) {
316 for op in ops.iter_mut() {
317 match op {
318 RtOp::Hole(RtSource::Child(_, k), _) => {
319 *k = *next;
320 *next += 1;
321 }
322 RtOp::Branch {
323 cond,
324 then,
325 otherwise,
326 } => {
327 if let RtSource::Child(_, k) = cond {
328 *k = *next;
329 *next += 1;
330 }
331 walk(then, next);
332 walk(otherwise, next);
333 }
334 RtOp::Repeat { body, .. } => {
335 let mut inner = 0;
336 walk(body, &mut inner);
337 }
338 _ => {}
339 }
340 }
341 }
342 let mut top = 0;
343 walk(ops, &mut top);
344}
345
346fn memoize(
349 ops: &[RtOp],
350 canonicals: &[Arc<PolydatKernel>],
351 memo: &mut [Option<Arc<[RuntimeTuple]>>],
352) {
353 for op in ops {
354 match op {
355 RtOp::Repeat {
356 stream,
357 child,
358 body,
359 generators,
360 ..
361 } => {
362 if generators.is_empty()
363 && !stream.text.contains('{')
364 && let Ok(tuples) = evaluate_for_iteration(&stream.ast, &*canonicals[*child])
365 {
366 memo[*child] = Some(tuples.into());
367 }
368 memoize(body, canonicals, memo);
369 }
370 RtOp::Branch {
371 then, otherwise, ..
372 } => {
373 memoize(then, canonicals, memo);
374 memoize(otherwise, canonicals, memo);
375 }
376 _ => {}
377 }
378 }
379}
380
381impl TileProgram {
382 pub fn from_parts(
389 spec: TileSpec,
390 bodies: Vec<Arc<crate::dsl::traversal::BodySource>>,
391 ) -> Result<Self, String> {
392 let mut children: Vec<Arc<PolydatProgram>> = Vec::with_capacity(bodies.len());
397 for (i, body) in bodies.iter().enumerate() {
398 let program = body
399 .program_on(crate::Engine::Interpreter(crate::JitMode::Auto))
400 .map_err(|e| format!("projection body {i} failed to compile: {e}"))?;
401 children.push(
402 program
403 .as_interpreter()
404 .ok_or_else(|| format!("projection body {i} is not an interpreter program"))?,
405 );
406 }
407 let canonicals: Vec<Arc<PolydatKernel>> = children
408 .iter()
409 .map(|p| Arc::new(PolydatKernel::from_program(p.clone())))
410 .collect();
411 let mut ops = lower_ops(&spec.ops);
412 number_child_holes(&mut ops);
413 let mut memo = vec![None; children.len()];
414 memoize(&ops, &canonicals, &mut memo);
415 Ok(TileProgram {
416 spec,
417 ops,
418 bodies,
419 children,
420 canonicals,
421 memo,
422 })
423 }
424
425 pub fn from_json(json: &str) -> Self {
434 let spec: TileSpec = serde_json::from_str(json)
435 .unwrap_or_else(|e| panic!("tile_render: malformed skeleton payload: {e}"));
436 let name = spec.name.clone();
437 let bodies: Vec<Arc<crate::dsl::traversal::BodySource>> = spec
438 .children
439 .iter()
440 .enumerate()
441 .map(|(i, c)| {
442 Arc::new(
443 crate::dsl::traversal::BodySource::from_source(
444 &c.source,
445 &format!("tile '{name}' :: projection body {i}"),
446 )
447 .unwrap_or_else(|e| {
448 panic!(
449 "tile '{name}': projection body failed to parse: {e}\n{}",
450 c.source
451 )
452 }),
453 )
454 })
455 .collect();
456 Self::from_parts(spec, bodies).unwrap_or_else(|e| panic!("tile '{name}': {e}"))
457 }
458
459 fn body_program_on(&self, child: usize, engine: crate::Engine) -> Arc<dyn KernelProgram> {
469 if matches!(engine, crate::Engine::Interpreter(_)) {
470 return self.children[child].clone();
471 }
472 self.bodies[child].program_on(engine).unwrap_or_else(|e| {
473 crate::library::support::audit::debug(&format!(
474 "tile '{}': projection body {child} renders on the interpreter: {e}",
475 self.spec.name
476 ));
477 self.children[child].clone()
478 })
479 }
480
481 pub fn has_projections(&self) -> bool {
483 fn walk(ops: &[RtOp]) -> bool {
484 ops.iter().any(|op| match op {
485 RtOp::Repeat { .. } => true,
486 RtOp::Branch {
487 then, otherwise, ..
488 } => walk(then) || walk(otherwise),
489 _ => false,
490 })
491 }
492 walk(&self.ops)
493 }
494
495 pub fn render(&self, inputs: &[Value], bodies: &mut BodyKernels) -> String {
499 let refs: Vec<ValueRef<'_>> = inputs.iter().map(ValueRef::from).collect();
500 let mut out = String::new();
501 self.render_into(
502 &refs,
503 crate::Engine::Interpreter(crate::JitMode::Auto),
504 bodies,
505 &mut out,
506 );
507 out
508 }
509
510 pub fn render_into<W: std::fmt::Write>(
517 &self,
518 inputs: &[ValueRef<'_>],
519 engine: crate::Engine,
520 bodies: &mut BodyKernels,
521 out: &mut W,
522 ) {
523 self.render_ops(&self.ops, inputs, engine, bodies, None, out);
524 }
525
526 fn render_ops<W: std::fmt::Write>(
527 &self,
528 ops: &[RtOp],
529 inputs: &[ValueRef<'_>],
530 engine: crate::Engine,
531 bodies: &mut BodyKernels,
532 mut child: Option<&mut BodyEntry>,
533 out: &mut W,
534 ) {
535 for op in ops {
536 match op {
537 RtOp::Copy(s) => out.put(s),
540 RtOp::Hole(source, enc) => match source {
541 RtSource::Wire(i) => {
542 encode_ref(inputs.get(*i).copied().unwrap_or(ValueRef::None), enc, out)
543 }
544 RtSource::Child(name, k) => {
545 if let Some(entry) = child.as_deref_mut()
546 && let Some(i) = entry.hole(*k, name)
547 {
548 let v = entry.kernel.pull_at(i);
549 encode_ref(ValueRef::from(&v), enc, out)
550 }
551 }
552 },
553 RtOp::Branch {
554 cond,
555 then,
556 otherwise,
557 } => {
558 let c = self.truthy(cond, inputs, child.as_deref_mut());
559 let branch = if c { then } else { otherwise };
560 self.render_ops(branch, inputs, engine, bodies, child.as_deref_mut(), out);
561 }
562 RtOp::Repeat {
563 stream,
564 child: child_idx,
565 sep,
566 body,
567 generators,
568 } => {
569 let memoized = self.memo[*child_idx].clone();
578 let tuples: std::borrow::Cow<'_, [RuntimeTuple]> = match &memoized {
579 Some(t) => std::borrow::Cow::Borrowed(&t[..]),
580 None => {
581 let mut streamer = (**stream).clone();
582 if !generators.is_empty() {
583 streamer.ast = bind_generators(&streamer.ast, generators, inputs);
584 }
585 std::borrow::Cow::Owned(
586 evaluate_for_iteration(
587 &streamer.ast,
588 &*self.canonicals[*child_idx],
589 )
590 .unwrap_or_else(|e| {
591 panic!(
592 "tile '{}': projection `for {}` failed at render: {e}",
593 self.spec.name, streamer.text
594 )
595 }),
596 )
597 }
598 };
599 let child_spec = &self.spec.children[*child_idx];
600 let program = self.body_program_on(*child_idx, engine);
606 let mut first = true;
607 let fail = |name: &str, e: crate::kernel::WriteError| -> ! {
608 panic!(
609 "tile '{}': projection body input `{name}`: {e}",
610 self.spec.name
611 )
612 };
613 bodies.with(&program, engine, |entry, bodies| {
614 for (index, tuple) in tuples.iter().enumerate() {
615 if !first {
616 out.put(sep);
617 }
618 first = false;
619 {
620 let BodyEntry {
623 kernel,
624 elements,
625 cascade,
626 ..
627 } = &mut *entry;
628 kernel.set_inputs(&[index as u64]);
629 let elements = elements.get_or_insert_with(|| {
630 tuple.iter().map(|(n, _)| kernel.input_index(n)).collect()
631 });
632 for (k, (name, v)) in tuple.iter().enumerate() {
633 if let Some(i) = elements.get(k).copied().flatten() {
634 kernel
635 .set_input_at(i, v.clone())
636 .unwrap_or_else(|e| fail(name, e));
637 }
638 }
639 let cascade = cascade.get_or_insert_with(|| {
640 child_spec
641 .cascade
642 .iter()
643 .map(|(n, _, _)| kernel.input_index(n))
644 .collect()
645 });
646 for (k, (name, input_idx, ty)) in
647 child_spec.cascade.iter().enumerate()
648 {
649 if let Some(i) = cascade.get(k).copied().flatten()
650 && let Some(v) = inputs.get(*input_idx)
651 {
652 kernel
653 .set_input_at(i, typed_for(&owned(*v), ty))
654 .unwrap_or_else(|e| fail(name, e));
655 }
656 }
657 }
658 self.render_ops(body, inputs, engine, bodies, Some(entry), out);
659 }
660 });
661 }
662 }
663 }
664 }
665
666 fn truthy(
668 &self,
669 source: &RtSource,
670 inputs: &[ValueRef<'_>],
671 child: Option<&mut BodyEntry>,
672 ) -> bool {
673 match source {
674 RtSource::Wire(i) => truthy_of(inputs.get(*i).copied().unwrap_or(ValueRef::None)),
675 RtSource::Child(name, k) => match child {
676 Some(entry) => match entry.hole(*k, name) {
677 Some(i) => truthy_of(ValueRef::from(&entry.kernel.pull_at(i))),
678 None => false,
679 },
680 None => false,
681 },
682 }
683 }
684}
685
686fn owned(v: ValueRef<'_>) -> Value {
689 match v {
690 ValueRef::U64(n) => Value::U64(n),
691 ValueRef::I64(n) => Value::I64(n),
692 ValueRef::F64(f) => Value::F64(f),
693 ValueRef::Bool(b) => Value::Bool(b),
694 ValueRef::Str(s) => Value::Str(Arc::from(s)),
695 ValueRef::Bytes(b) => Value::Bytes(Arc::from(b)),
696 ValueRef::Json(j) => Value::Json(Arc::new(j.clone())),
697 ValueRef::None => Value::None,
698 ValueRef::Other(v) => v.clone(),
699 }
700}
701
702fn typed_for(v: &Value, ty: &str) -> Value {
706 match (v, PortType::from_keyword(ty)) {
707 (Value::Str(_), Some(t)) if t != PortType::Str => retype(v, ty),
708 _ => v.clone(),
709 }
710}
711
712fn bind_generators(
717 c: &crate::iteration::comprehension::Comprehension,
718 generators: &[(String, usize, String)],
719 inputs: &[ValueRef<'_>],
720) -> crate::iteration::comprehension::Comprehension {
721 use crate::iteration::comprehension::Comprehension as K;
722 use crate::iteration::comprehension::source::{LiteralValue, Source};
723 match c {
724 K::Clause {
725 name,
726 source: Source::Generator { .. },
727 } => {
728 let Some((_, idx, ty)) = generators.iter().find(|(n, _, _)| n == name) else {
729 return c.clone();
730 };
731 let raw = inputs.get(*idx).map(|v| owned(*v)).unwrap_or(Value::None);
732 let items: Vec<Value> =
733 match crate::iteration::comprehension::source_values::iteration_interior(&raw) {
734 Some(interior) => interior,
735 None => match &raw {
736 Value::Str(text) => {
737 match serde_json::from_str::<serde_json::Value>(text.trim()) {
738 Ok(serde_json::Value::Array(items)) => items
739 .iter()
740 .map(|j| {
741 retype(
742 &Value::Str(j.to_string().trim_matches('"').into()),
743 ty,
744 )
745 })
746 .collect(),
747 _ => vec![typed_for(&raw, ty)],
748 }
749 }
750 _ => vec![raw.clone()],
751 },
752 };
753 let json_items = ty == "json";
757 let values = items
758 .iter()
759 .map(|v| {
760 if json_items {
761 return LiteralValue::Json(json_of(v));
762 }
763 match v {
764 Value::U64(n) => LiteralValue::Int(*n as i64),
765 Value::I64(n) => LiteralValue::Int(*n),
766 Value::F64(f) => LiteralValue::Float(*f),
767 Value::Bool(b) => LiteralValue::Bool(*b),
768 Value::Json(j) => match j.as_ref() {
770 serde_json::Value::Number(n) if n.is_u64() => {
771 LiteralValue::Int(n.as_u64().unwrap_or(0) as i64)
772 }
773 serde_json::Value::Number(n) if n.is_i64() => {
774 LiteralValue::Int(n.as_i64().unwrap_or(0))
775 }
776 serde_json::Value::Number(n) => {
777 LiteralValue::Float(n.as_f64().unwrap_or(0.0))
778 }
779 serde_json::Value::Bool(b) => LiteralValue::Bool(*b),
780 serde_json::Value::String(s) => LiteralValue::String(s.clone()),
781 other => LiteralValue::String(other.to_string()),
782 },
783 other => LiteralValue::String(other.to_display_string()),
784 }
785 })
786 .collect();
787 K::Clause {
788 name: name.clone(),
789 source: Source::Literal { values },
790 }
791 }
792 K::Clause { .. } => c.clone(),
793 K::Cartesian { children } => K::Cartesian {
794 children: children
795 .iter()
796 .map(|ch| bind_generators(ch, generators, inputs))
797 .collect(),
798 },
799 K::Zip { children, mode } => K::Zip {
800 children: children
801 .iter()
802 .map(|ch| bind_generators(ch, generators, inputs))
803 .collect(),
804 mode: *mode,
805 },
806 K::Union { children } => K::Union {
807 children: children
808 .iter()
809 .map(|ch| bind_generators(ch, generators, inputs))
810 .collect(),
811 },
812 K::Filter { child, predicate } => K::Filter {
813 child: Box::new(bind_generators(child, generators, inputs)),
814 predicate: predicate.clone(),
815 },
816 K::Order {
817 child,
818 strategy,
819 truncation,
820 seed,
821 } => K::Order {
822 child: Box::new(bind_generators(child, generators, inputs)),
823 strategy: *strategy,
824 truncation: *truncation,
825 seed: *seed,
826 },
827 }
828}
829
830fn json_of(v: &Value) -> serde_json::Value {
835 match v {
836 Value::Json(j) => j.as_ref().clone(),
837 Value::U64(n) => serde_json::Value::from(*n),
838 Value::I64(n) => serde_json::Value::from(*n),
839 Value::F64(f) => serde_json::Number::from_f64(*f)
840 .map(serde_json::Value::Number)
841 .unwrap_or(serde_json::Value::Null),
842 Value::Bool(b) => serde_json::Value::Bool(*b),
843 Value::Str(s) => serde_json::Value::String(s.to_string()),
844 Value::None => serde_json::Value::Null,
845 other => serde_json::Value::String(other.to_display_string()),
846 }
847}
848
849fn retype(v: &Value, ty: &str) -> Value {
850 let text = v.to_display_string();
851 match PortType::from_keyword(ty) {
852 Some(PortType::U64) => text.parse().map(Value::U64).unwrap_or(Value::None),
853 Some(PortType::F64) => text.parse().map(Value::F64).unwrap_or(Value::None),
854 Some(PortType::Bool) => Value::Bool(matches!(text.trim(), "true" | "1")),
855 Some(PortType::Str) | None => Value::Str(text.into()),
856 Some(_) => v.clone(),
857 }
858}
859
860struct BodyEntry {
864 program: Arc<dyn KernelProgram>,
865 kernel: Box<dyn Kernel>,
866 elements: Option<Vec<Option<usize>>>,
869 cascade: Option<Vec<Option<usize>>>,
871 holes: Vec<Option<Option<usize>>>,
873}
874
875impl BodyEntry {
876 fn hole(&mut self, k: usize, name: &str) -> Option<usize> {
878 if self.holes.len() <= k {
879 self.holes.resize(k + 1, None);
880 }
881 if self.holes[k].is_none() {
882 self.holes[k] = Some(self.kernel.output_index(name));
883 }
884 self.holes[k].flatten()
885 }
886}
887
888#[derive(Default)]
896pub struct BodyKernels {
897 entries: HashMap<(usize, crate::Engine), BodyEntry>,
898 created: u64,
900}
901
902impl Clone for BodyKernels {
903 fn clone(&self) -> Self {
904 Self::default()
905 }
906}
907
908unsafe impl Sync for BodyKernels {}
915
916impl std::fmt::Debug for BodyKernels {
917 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
918 f.debug_struct("BodyKernels")
919 .field("entries", &self.entries.len())
920 .field("created", &self.created)
921 .finish()
922 }
923}
924
925fn body_engine_key(engine: crate::Engine) -> crate::Engine {
928 match engine {
929 crate::Engine::Interpreter(_) => crate::Engine::Interpreter(crate::JitMode::Auto),
930 other => other,
931 }
932}
933
934impl BodyKernels {
935 pub fn created(&self) -> u64 {
937 self.created
938 }
939
940 #[cfg(test)]
942 fn clone_for_test(&self) -> (u64, BodyKernels) {
943 (self.created, self.clone())
944 }
945
946 fn with(
950 &mut self,
951 program: &Arc<dyn KernelProgram>,
952 engine: crate::Engine,
953 f: impl FnOnce(&mut BodyEntry, &mut BodyKernels),
954 ) {
955 let engine = body_engine_key(engine);
956 let key = (Arc::as_ptr(program) as *const () as usize, engine);
959 let mut entry = self
960 .entries
961 .remove(&key)
962 .filter(|e| Arc::ptr_eq(&e.program, program))
963 .unwrap_or_else(|| {
964 self.created += 1;
965 BodyEntry {
966 program: program.clone(),
967 kernel: program.clone().create_kernel(),
968 elements: None,
969 cascade: None,
970 holes: Vec::new(),
971 }
972 });
973 f(&mut entry, self);
974 self.entries.insert(key, entry);
975 }
976}
977
978pub(crate) mod render_state {
980 use super::{BodyKernels, TileRender};
981 use crate::ast::{ScratchBuf, ScratchElem, Value};
982
983 pub(crate) fn layout(_node: &TileRender) -> Vec<ScratchElem> {
984 vec![ScratchElem::Kernels]
985 }
986
987 pub(crate) fn eval(
988 node: &TileRender,
989 scratch: &mut [ScratchBuf],
990 inputs: &[Value],
991 outputs: &mut [Value],
992 ) {
993 let bodies = bodies_of(&mut scratch[0]);
994 outputs[0] = Value::Str(node.program.render(inputs, bodies).into());
995 }
996
997 pub(crate) fn bodies_of(entry: &mut ScratchBuf) -> &mut BodyKernels {
999 match entry {
1000 ScratchBuf::Kernels(b) => b,
1001 other => panic!("a tile render's scratch holds {other:?}, not its body kernels"),
1002 }
1003 }
1004}
1005
1006pub(crate) trait Sink: std::fmt::Write {
1010 fn put(&mut self, s: &str) {
1011 let _ = self.write_str(s);
1012 }
1013 fn put_char(&mut self, c: char) {
1014 let _ = self.write_char(c);
1015 }
1016}
1017
1018impl<W: std::fmt::Write> Sink for W {}
1019
1020pub fn encode<W: std::fmt::Write>(value: &Value, enc: &HoleEncoding, out: &mut W) {
1022 encode_ref(ValueRef::from(value), enc, out)
1023}
1024
1025pub fn encode_ref<W: std::fmt::Write>(value: ValueRef<'_>, enc: &HoleEncoding, out: &mut W) {
1029 if enc.cond {
1030 out.put_char(if truthy_of(value) { '1' } else { '0' });
1031 return;
1032 }
1033 let ty = enc.ty.as_deref();
1034 if is_numeric_keyword(ty.unwrap_or("u64")) {
1043 match (enc.format.as_deref(), value) {
1044 (None, ValueRef::U64(n)) => {
1045 put_u64(n, out);
1046 return;
1047 }
1048 (None, ValueRef::I64(n)) => {
1049 if n < 0 {
1050 out.put_char('-');
1051 }
1052 put_u64(n.unsigned_abs(), out);
1053 return;
1054 }
1055 (None, ValueRef::F64(f)) => {
1056 let _ = float_text::write_shortest(f, out);
1057 return;
1058 }
1059 (Some(fmt), ValueRef::F64(_) | ValueRef::U64(_)) => {
1060 if let (Some(prec), Some(f)) = (precision_of(fmt), as_f64(value)) {
1061 let _ = float_text::write_fixed(f, prec, out);
1062 return;
1063 }
1064 }
1065 _ => {}
1066 }
1067 }
1068 let text = formatted_text(value, ty, enc.format.as_deref());
1069 if enc.raw {
1070 out.put(&text);
1071 return;
1072 }
1073 match (enc.encoding.as_str(), enc.position) {
1074 ("json", HolePosition::InString) => push_json_escaped(&text, out),
1075 ("json", HolePosition::Value) => {
1076 let kind = ty.unwrap_or_else(|| value.port_type().to_keyword());
1077 match (kind, value) {
1078 (_, ValueRef::None) => out.put("null"),
1079 ("bool", _) => out.put(if truthy_of(value) { "true" } else { "false" }),
1080 ("json", ValueRef::Json(j)) => {
1081 let _ = write!(out, "{j}");
1082 }
1083 ("str", _) | ("String", _) | ("string", _) => {
1084 out.put_char('"');
1085 push_json_escaped(&text, out);
1086 out.put_char('"');
1087 }
1088 (k, _) if is_numeric_keyword(k) => out.put(&text),
1089 (_, ValueRef::Json(j)) => {
1090 let _ = write!(out, "{j}");
1091 }
1092 (_, ValueRef::Bool(b)) => out.put(if b { "true" } else { "false" }),
1093 (_, ValueRef::U64(_)) | (_, ValueRef::F64(_)) => out.put(&text),
1094 _ => {
1095 out.put_char('"');
1096 push_json_escaped(&text, out);
1097 out.put_char('"');
1098 }
1099 }
1100 }
1101 ("csv", _) => {
1102 if text.contains([',', '"', '\n']) {
1103 out.put_char('"');
1104 for (i, piece) in text.split('"').enumerate() {
1105 if i > 0 {
1106 out.put("\"\"");
1107 }
1108 out.put(piece);
1109 }
1110 out.put_char('"');
1111 } else {
1112 out.put(&text);
1113 }
1114 }
1115 _ => out.put(&text),
1116 }
1117}
1118
1119fn put_u64<W: std::fmt::Write>(mut n: u64, out: &mut W) {
1121 if n == 0 {
1122 out.put_char('0');
1123 return;
1124 }
1125 let mut buf = [0u8; 20];
1126 let mut i = buf.len();
1127 while n > 0 {
1128 i -= 1;
1129 buf[i] = b'0' + (n % 10) as u8;
1130 n /= 10;
1131 }
1132 out.put(std::str::from_utf8(&buf[i..]).expect("ascii digits"));
1134}
1135
1136fn truthy_of(v: ValueRef<'_>) -> bool {
1137 match v {
1138 ValueRef::Bool(b) => b,
1139 ValueRef::U64(n) => n != 0,
1140 ValueRef::F64(f) => f != 0.0,
1141 ValueRef::Str(s) => !s.is_empty() && s != "0" && s != "false",
1142 ValueRef::None => false,
1143 _ => true,
1144 }
1145}
1146
1147fn is_numeric_keyword(k: &str) -> bool {
1148 matches!(
1149 k,
1150 "u64"
1151 | "i64"
1152 | "f64"
1153 | "f32"
1154 | "u32"
1155 | "i32"
1156 | "u16"
1157 | "i16"
1158 | "u8"
1159 | "i8"
1160 | "u128"
1161 | "i128"
1162 | "f16"
1163 )
1164}
1165
1166fn formatted_text<'a>(
1170 value: ValueRef<'a>,
1171 ty: Option<&str>,
1172 format: Option<&str>,
1173) -> std::borrow::Cow<'a, str> {
1174 use std::borrow::Cow;
1175 let base = |value: ValueRef<'a>| -> Cow<'a, str> {
1179 match (ty, value) {
1180 (Some("bool"), v) => Cow::Owned(truthy_of(v).to_string()),
1181 (_, ValueRef::Json(serde_json::Value::String(s))) => Cow::Owned(s.clone()),
1184 (_, ValueRef::Json(j)) => Cow::Owned(j.to_string()),
1185 (_, v) => v.display(),
1186 }
1187 };
1188 let Some(fmt) = format else {
1189 return base(value);
1190 };
1191 let fmt = fmt.trim();
1192 if let Some(prec) = precision_of(fmt) {
1193 if let Some(f) = as_f64(value) {
1194 return Cow::Owned(float_text::fixed_string(f, prec));
1195 }
1196 return base(value);
1197 }
1198 if fmt == "x" || fmt == "X" {
1199 if let ValueRef::U64(n) = value {
1200 return Cow::Owned(if fmt == "x" {
1201 format!("{n:x}")
1202 } else {
1203 format!("{n:X}")
1204 });
1205 }
1206 return base(value);
1207 }
1208 let base = base(value);
1209 if let Some(w) = fmt.strip_prefix('0').and_then(|w| w.parse::<usize>().ok()) {
1210 return Cow::Owned(format!("{base:0>w$}"));
1211 }
1212 if let Some(w) = fmt.strip_prefix('>').and_then(|w| w.parse::<usize>().ok()) {
1213 return Cow::Owned(format!("{base:>w$}"));
1214 }
1215 if let Some(w) = fmt.strip_prefix('<').and_then(|w| w.parse::<usize>().ok()) {
1216 return Cow::Owned(format!("{base:<w$}"));
1217 }
1218 if let Ok(w) = fmt.parse::<usize>() {
1219 return Cow::Owned(format!("{base:>w$}"));
1220 }
1221 base
1222}
1223
1224fn as_f64(v: ValueRef<'_>) -> Option<f64> {
1225 match v {
1226 ValueRef::F64(f) => Some(f),
1227 ValueRef::U64(n) => Some(n as f64),
1228 _ => None,
1229 }
1230}
1231
1232fn precision_of(fmt: &str) -> Option<usize> {
1234 fmt.trim()
1235 .strip_prefix('.')
1236 .and_then(|p| p.parse::<usize>().ok())
1237}
1238
1239fn push_json_escaped<W: std::fmt::Write>(s: &str, out: &mut W) {
1240 for c in s.chars() {
1241 match c {
1242 '"' => out.put("\\\""),
1243 '\\' => out.put("\\\\"),
1244 '\n' => out.put("\\n"),
1245 '\r' => out.put("\\r"),
1246 '\t' => out.put("\\t"),
1247 c if (c as u32) < 0x20 => {
1248 let _ = write!(out, "\\u{:04x}", c as u32);
1249 }
1250 c => out.put_char(c),
1251 }
1252 }
1253}
1254
1255#[crate::polydat_node(category = Formatting)]
1263fn tile_encode(
1264 value: Value,
1265 spec: Const<&str>,
1266 #[poly_const(HoleEncoding::from_spec, from = spec)] enc: &HoleEncoding,
1267) -> String {
1268 let mut out = String::new();
1269 encode(&value, enc, &mut out);
1270 out
1271}
1272
1273fn tile_render_compiled(
1281 node: &TileRender,
1282 wire_types: &[PortType],
1283 engine: crate::Engine,
1284) -> crate::ast::CompiledSlotKit {
1285 let program: &'static TileProgram = unsafe { &*std::sync::Arc::into_raw(node.program.clone()) };
1291 let mut reads: Vec<(usize, PortType)> = Vec::with_capacity(wire_types.len());
1294 let mut offset = 0usize;
1295 for &ty in wire_types {
1296 reads.push((offset, ty));
1297 offset += ty.slot_width().max(1);
1298 }
1299 crate::ast::CompiledSlotKit {
1300 scratch: vec![
1301 crate::ast::ScratchElem::Str,
1302 crate::ast::ScratchElem::Kernels,
1303 ],
1304 op: Box::new(
1305 move |inputs: &[u64], outputs: &mut [u64], scratch: &mut [crate::ast::ScratchBuf]| {
1306 let owned_values: Vec<Value> = reads
1310 .iter()
1311 .filter(|(_, ty)| ty.slot_color() == crate::ast::SlotColor::Imm2)
1312 .map(|&(offset, ty)| crate::compile::marshal::decode_output(inputs, offset, ty))
1313 .collect();
1314 let mut next_owned = 0usize;
1315 let refs: Vec<ValueRef<'_>> = reads
1316 .iter()
1317 .map(|&(offset, ty)| {
1318 if ty.slot_color() == crate::ast::SlotColor::Imm2 {
1319 let v = ValueRef::from(&owned_values[next_owned]);
1320 next_owned += 1;
1321 v
1322 } else {
1323 unsafe { crate::compile::marshal::arg_ref(ty, &inputs[offset..]) }
1326 }
1327 })
1328 .collect();
1329 let (text, bodies) = scratch.split_at_mut(1);
1338 let crate::ast::ScratchBuf::Str(buf) = &mut text[0] else {
1339 unreachable!("the render step owns a string entry");
1340 };
1341 let bodies = render_state::bodies_of(&mut bodies[0]);
1342 buf.clear();
1343 let mut w = BytesSink(buf);
1344 program.render_into(&refs, engine, bodies, &mut w);
1345 let (p, l) = scratch[0].ptr_len();
1346 outputs[0] = p;
1347 outputs[1] = l;
1348 },
1349 ),
1350 }
1351}
1352
1353pub(crate) struct BytesSink<'a>(pub(crate) &'a mut Vec<u8>);
1356
1357impl std::fmt::Write for BytesSink<'_> {
1358 fn write_str(&mut self, s: &str) -> std::fmt::Result {
1359 self.0.extend_from_slice(s.as_bytes());
1360 Ok(())
1361 }
1362}
1363
1364#[crate::polydat_node(
1375 category = Formatting,
1376 variadic_min = 0,
1377 compiled_slot = tile_render_compiled,
1378 state = render_state
1379)]
1380fn tile_render(program: Const<Arc<TileProgram>>, values: &[Value]) -> String {
1381 program.render(values, &mut BodyKernels::default())
1384}
1385
1386#[cfg(test)]
1387mod tests {
1388 use super::*;
1389
1390 #[test]
1394 fn body_kernels_are_created_once_per_state_and_reused() {
1395 let src =
1396 "input cycle: u64\ntile t : text := \"@for k in 0..3 sep \\\",\\\" {${k + cycle}}\"\n";
1397 let mut k = crate::dsl::compile_polydat_interpreter(src).unwrap();
1398 let program = k.program();
1399 let node = (0..program.node_count())
1400 .find(|&i| program.node_meta(i).name == "tile_render")
1401 .expect("the tile's render node");
1402 let bodies_of = |k: &mut PolydatKernel| match &k.state().core.node_scratch[node][0] {
1403 crate::ast::ScratchBuf::Kernels(b) => b.clone_for_test(),
1404 other => panic!("{other:?}"),
1405 };
1406 assert_eq!(bodies_of(&mut k).0, 0, "nothing before the first render");
1407 k.set_inputs(&[10]);
1408 assert_eq!(k.pull_ref("t").as_str(), "10,11,12");
1409 assert_eq!(bodies_of(&mut k).0, 1, "one kernel for the body");
1410 for c in 0..5u64 {
1411 k.set_inputs(&[c]);
1412 let _ = k.pull_ref("t");
1413 }
1414 let (created, clone) = bodies_of(&mut k);
1415 assert_eq!(created, 1, "reused across renders");
1416 assert_eq!(clone.created(), 0, "a clone is a new state's empty set");
1417 }
1418
1419 fn enc(
1420 encoding: &str,
1421 position: HolePosition,
1422 ty: Option<&str>,
1423 format: Option<&str>,
1424 raw: bool,
1425 ) -> HoleEncoding {
1426 HoleEncoding {
1427 encoding: encoding.into(),
1428 position,
1429 ty: ty.map(str::to_string),
1430 format: format.map(str::to_string),
1431 raw,
1432 cond: false,
1433 }
1434 }
1435
1436 #[test]
1437 fn json_value_and_string_positions_encode_by_type() {
1438 let mut out = String::new();
1439 encode(
1440 &Value::Str("a\"b".into()),
1441 &enc("json", HolePosition::Value, Some("str"), None, false),
1442 &mut out,
1443 );
1444 assert_eq!(out, "\"a\\\"b\"");
1445 out.clear();
1446 encode(
1447 &Value::U64(7),
1448 &enc("json", HolePosition::Value, None, None, false),
1449 &mut out,
1450 );
1451 assert_eq!(out, "7");
1452 out.clear();
1453 encode(
1454 &Value::Str("x\ny".into()),
1455 &enc("json", HolePosition::InString, None, None, false),
1456 &mut out,
1457 );
1458 assert_eq!(out, "x\\ny");
1459 out.clear();
1460 encode(
1461 &Value::F64(2.0 / 3.0),
1462 &enc("json", HolePosition::Value, None, Some(".2"), false),
1463 &mut out,
1464 );
1465 assert_eq!(out, "0.67");
1466 out.clear();
1467 encode(
1468 &Value::None,
1469 &enc("json", HolePosition::Value, None, None, false),
1470 &mut out,
1471 );
1472 assert_eq!(out, "null");
1473 }
1474
1475 #[test]
1476 fn spec_round_trips() {
1477 let e = enc(
1478 "json",
1479 HolePosition::InString,
1480 Some("u64"),
1481 Some(".2"),
1482 true,
1483 );
1484 assert_eq!(HoleEncoding::from_spec(&e.to_spec()), e);
1485 let c = HoleEncoding {
1486 cond: true,
1487 ..enc("text", HolePosition::Text, None, None, false)
1488 };
1489 assert_eq!(HoleEncoding::from_spec(&c.to_spec()), c);
1490 }
1491
1492 #[test]
1493 fn csv_quotes_when_needed_and_raw_skips_escaping() {
1494 let mut out = String::new();
1495 encode(
1496 &Value::Str("a,b".into()),
1497 &enc("csv", HolePosition::Text, None, None, false),
1498 &mut out,
1499 );
1500 assert_eq!(out, "\"a,b\"");
1501 out.clear();
1502 encode(
1503 &Value::Str("a\"b".into()),
1504 &enc("json", HolePosition::Value, None, None, true),
1505 &mut out,
1506 );
1507 assert_eq!(out, "a\"b");
1508 }
1509
1510 #[test]
1511 fn formats_apply_before_encoding() {
1512 assert_eq!(formatted_text(ValueRef::U64(5), None, Some("03")), "005");
1513 assert_eq!(formatted_text(ValueRef::U64(255), None, Some("x")), "ff");
1514 assert_eq!(
1515 formatted_text(ValueRef::Str("ab"), None, Some(">4")),
1516 " ab"
1517 );
1518 assert_eq!(
1519 formatted_text(ValueRef::F64(0.295), None, Some(".2")),
1520 "0.29"
1521 );
1522 assert_eq!(
1523 formatted_text(ValueRef::U64(7), None, Some(" .3 ")),
1524 "7.000"
1525 );
1526 }
1527
1528 #[test]
1531 fn float_holes_write_rust_text() {
1532 let cases: [(f64, Option<&str>, &str); 8] = [
1533 (100.0, None, "100.0"),
1534 (0.1, None, "0.1"),
1535 (5e-5, None, "5e-5"),
1536 (1e16, None, "1e16"),
1537 (-0.0, None, "-0.0"),
1538 (2.0 / 3.0, Some(".2"), "0.67"),
1539 (0.295, Some(".2"), "0.29"),
1540 (2.5, Some(".0"), "2"),
1541 ];
1542 for (f, fmt, want) in cases {
1543 for (encoding, position) in [
1544 ("text", HolePosition::Text),
1545 ("json", HolePosition::Value),
1546 ("json", HolePosition::InString),
1547 ("csv", HolePosition::Text),
1548 ] {
1549 for ty in [None, Some("f64")] {
1550 let mut out = String::new();
1551 encode(
1552 &Value::F64(f),
1553 &enc(encoding, position, ty, fmt, false),
1554 &mut out,
1555 );
1556 assert_eq!(out, want, "{f:?} {fmt:?} {encoding} {position:?} {ty:?}");
1557 }
1558 }
1559 let mut out = String::new();
1562 encode(
1563 &Value::F64(f),
1564 &enc("json", HolePosition::Value, Some("str"), fmt, false),
1565 &mut out,
1566 );
1567 assert_eq!(out, format!("\"{want}\""));
1568 }
1569 }
1570
1571 #[test]
1575 fn a_tile_reports_whether_any_op_re_runs_a_projection() {
1576 fn wire(index: usize) -> HoleSource {
1577 HoleSource::Wire {
1578 index,
1579 spec: "text|text".into(),
1580 }
1581 }
1582 fn program(ops: Vec<TileOp>, children: Vec<ChildSpec>) -> TileProgram {
1583 TileProgram::from_json(
1584 &TileSpec {
1585 name: "t".into(),
1586 encoding: "text".into(),
1587 ops,
1588 children,
1589 }
1590 .to_json(),
1591 )
1592 }
1593 let body = || ChildSpec {
1594 source: "input cycle: u64\nextern k: u64\nout := u64_add(k, 0)\n".to_string(),
1595 cascade: Vec::new(),
1596 };
1597 let repeat = |child: usize| TileOp::Repeat {
1598 stream: StreamerValue::parse_text("k in 0..3").unwrap().to_json(),
1599 child,
1600 sep: ",".into(),
1601 body: vec![TileOp::Hole(HoleSource::Child {
1602 name: "out".into(),
1603 spec: "text|text".into(),
1604 })],
1605 generators: Vec::new(),
1606 };
1607
1608 let flat = program(
1610 vec![
1611 TileOp::Static("a=".into()),
1612 TileOp::Hole(wire(0)),
1613 TileOp::Branch {
1614 cond: wire(1),
1615 then: vec![TileOp::Static("yes".into())],
1616 otherwise: vec![TileOp::Hole(wire(0))],
1617 },
1618 ],
1619 Vec::new(),
1620 );
1621 assert!(!flat.has_projections());
1622 assert!(flat.children.is_empty());
1623
1624 let top = program(vec![TileOp::Static("[".into()), repeat(0)], vec![body()]);
1626 assert!(top.has_projections());
1627 assert_eq!(top.children.len(), 1);
1628
1629 for (then, otherwise) in [
1632 (vec![repeat(0)], vec![TileOp::Static("none".into())]),
1633 (vec![TileOp::Static("none".into())], vec![repeat(0)]),
1634 ] {
1635 let branched = program(
1636 vec![TileOp::Branch {
1637 cond: wire(0),
1638 then,
1639 otherwise,
1640 }],
1641 vec![body()],
1642 );
1643 assert!(branched.has_projections());
1644 }
1645 }
1646}