use std::collections::HashMap;
use std::sync::Arc;
use serde::{Deserialize, Serialize};
use crate::ast::SlotShape;
use crate::ast::{PortType, Value, ValueRef};
use crate::iteration::comprehension::StreamerValue;
use crate::iteration::comprehension::runtime::{RuntimeTuple, evaluate_for_iteration};
use crate::kernel::{Kernel, KernelProgram, PolydatKernel, PolydatProgram};
use crate::library::support::float_text;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum HolePosition {
Value,
InString,
Text,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HoleEncoding {
pub encoding: String,
pub position: HolePosition,
pub ty: Option<String>,
pub format: Option<String>,
pub raw: bool,
pub cond: bool,
}
impl HoleEncoding {
pub fn to_spec(&self) -> String {
let pos = match self.position {
HolePosition::Value => "value",
HolePosition::InString => "string",
HolePosition::Text => "text",
};
let mut flags = String::new();
if self.raw {
flags.push('r');
}
if self.cond {
flags.push('c');
}
format!(
"{}|{}|{}|{}|{}",
self.encoding,
pos,
self.ty.as_deref().unwrap_or(""),
self.format.as_deref().unwrap_or(""),
flags
)
}
pub fn interned(spec: &str) -> &'static HoleEncoding {
use std::sync::RwLock;
static ENCODINGS: RwLock<Option<HashMap<String, &'static HoleEncoding>>> =
RwLock::new(None);
if let Some(e) = ENCODINGS
.read()
.unwrap()
.as_ref()
.and_then(|m| m.get(spec).copied())
{
return e;
}
let mut guard = ENCODINGS.write().unwrap();
let map = guard.get_or_insert_with(HashMap::new);
if let Some(e) = map.get(spec).copied() {
return e;
}
let leaked: &'static HoleEncoding = Box::leak(Box::new(Self::from_spec(spec)));
map.insert(spec.to_string(), leaked);
leaked
}
pub fn from_spec(spec: &str) -> Self {
let mut parts = spec.splitn(5, '|');
let encoding = parts.next().unwrap_or("text").to_string();
let position = match parts.next().unwrap_or("text") {
"value" => HolePosition::Value,
"string" => HolePosition::InString,
_ => HolePosition::Text,
};
let ty = parts.next().filter(|s| !s.is_empty()).map(str::to_string);
let format = parts.next().filter(|s| !s.is_empty()).map(str::to_string);
let flags = parts.next().unwrap_or("");
HoleEncoding {
encoding,
position,
ty,
format,
raw: flags.contains('r'),
cond: flags.contains('c'),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum HoleSource {
Wire {
index: usize,
spec: String,
},
Child {
name: String,
spec: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum TileOp {
Static(String),
Hole(HoleSource),
Repeat {
stream: String,
child: usize,
sep: String,
body: Vec<TileOp>,
#[serde(default)]
generators: Vec<(String, usize, String)>,
},
Branch {
cond: HoleSource,
then: Vec<TileOp>,
otherwise: Vec<TileOp>,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ChildSpec {
pub source: String,
pub cascade: Vec<(String, usize, String)>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TileSpec {
pub name: String,
pub encoding: String,
pub ops: Vec<TileOp>,
pub children: Vec<ChildSpec>,
}
impl TileSpec {
pub fn to_json(&self) -> String {
serde_json::to_string(self).expect("TileSpec serializes")
}
}
#[derive(Debug)]
enum RtOp {
Copy(&'static str),
Hole(RtSource, HoleEncoding),
Repeat {
stream: Arc<StreamerValue>,
child: usize,
sep: &'static str,
body: Vec<RtOp>,
generators: Vec<(String, usize, String)>,
},
Branch {
cond: RtSource,
then: Vec<RtOp>,
otherwise: Vec<RtOp>,
},
}
#[derive(Debug)]
enum RtSource {
Wire(usize),
Child(String, usize),
}
fn lower_source(source: &HoleSource) -> (RtSource, HoleEncoding) {
match source {
HoleSource::Wire { index, spec } => (RtSource::Wire(*index), HoleEncoding::from_spec(spec)),
HoleSource::Child { name, spec } => (
RtSource::Child(name.clone(), 0),
HoleEncoding::from_spec(spec),
),
}
}
fn lower_ops(ops: &[TileOp]) -> Vec<RtOp> {
use crate::kernel::StaticInterner;
ops.iter()
.map(|op| match op {
TileOp::Static(s) => RtOp::Copy(StaticInterner::intern(s)),
TileOp::Hole(h) => {
let (source, enc) = lower_source(h);
RtOp::Hole(source, enc)
}
TileOp::Repeat {
stream,
child,
sep,
body,
generators,
} => RtOp::Repeat {
stream: Arc::new(StreamerValue::from_json(stream)),
child: *child,
sep: StaticInterner::intern(sep),
body: lower_ops(body),
generators: generators.clone(),
},
TileOp::Branch {
cond,
then,
otherwise,
} => RtOp::Branch {
cond: lower_source(cond).0,
then: lower_ops(then),
otherwise: lower_ops(otherwise),
},
})
.collect()
}
pub struct TileProgram {
pub spec: TileSpec,
ops: Vec<RtOp>,
pub children: Vec<Arc<PolydatProgram>>,
canonicals: Vec<Arc<PolydatKernel>>,
compiled: Vec<Option<Arc<dyn KernelProgram>>>,
memo: Vec<Option<Arc<[RuntimeTuple]>>>,
}
impl std::fmt::Debug for TileProgram {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TileProgram")
.field("spec", &self.spec)
.field("ops", &self.ops)
.field("children", &self.children.len())
.finish_non_exhaustive()
}
}
fn number_child_holes(ops: &mut [RtOp]) {
fn walk(ops: &mut [RtOp], next: &mut usize) {
for op in ops.iter_mut() {
match op {
RtOp::Hole(RtSource::Child(_, k), _) => {
*k = *next;
*next += 1;
}
RtOp::Branch {
cond,
then,
otherwise,
} => {
if let RtSource::Child(_, k) = cond {
*k = *next;
*next += 1;
}
walk(then, next);
walk(otherwise, next);
}
RtOp::Repeat { body, .. } => {
let mut inner = 0;
walk(body, &mut inner);
}
_ => {}
}
}
}
let mut top = 0;
walk(ops, &mut top);
}
fn memoize(
ops: &[RtOp],
canonicals: &[Arc<PolydatKernel>],
memo: &mut [Option<Arc<[RuntimeTuple]>>],
) {
for op in ops {
match op {
RtOp::Repeat {
stream,
child,
body,
generators,
..
} => {
if generators.is_empty()
&& !stream.text.contains('{')
&& let Ok(tuples) = evaluate_for_iteration(
&stream.ast,
&*canonicals[*child],
&HashMap::new(),
|_| Ok(()),
)
{
memo[*child] = Some(tuples.into());
}
memoize(body, canonicals, memo);
}
RtOp::Branch {
then, otherwise, ..
} => {
memoize(then, canonicals, memo);
memoize(otherwise, canonicals, memo);
}
_ => {}
}
}
}
impl TileProgram {
pub fn from_json(json: &str) -> Self {
let spec: TileSpec = serde_json::from_str(json)
.unwrap_or_else(|e| panic!("tile_render: malformed skeleton payload: {e}"));
let children: Vec<Arc<PolydatProgram>> = spec
.children
.iter()
.map(|c| {
crate::dsl::compile_polydat(&c.source)
.unwrap_or_else(|e| {
panic!(
"tile '{}': projection body failed to compile: {e}\n{}",
spec.name, c.source
)
})
.into_program()
})
.collect();
let canonicals: Vec<Arc<PolydatKernel>> = children
.iter()
.map(|p| Arc::new(PolydatKernel::from_program(p.clone())))
.collect();
let mut ops = lower_ops(&spec.ops);
number_child_holes(&mut ops);
let mut memo = vec![None; children.len()];
memoize(&ops, &canonicals, &mut memo);
let compiled = spec
.children
.iter()
.enumerate()
.map(|(i, c)| {
match crate::dsl::compile::compile_polydat_with(&c.source, crate::Engine::default())
{
Ok(kernel) => Some(kernel.into_program()),
Err(e) => {
crate::library::support::audit::debug(&format!(
"tile '{}': projection body {i} renders on the interpreter: {e}",
spec.name
));
None
}
}
})
.collect();
TileProgram {
spec,
ops,
children,
canonicals,
compiled,
memo,
}
}
fn body_program_on(&self, child: usize, engine: crate::Engine) -> Arc<dyn KernelProgram> {
if matches!(engine, crate::Engine::Interpreter(_)) {
return self.children[child].clone();
}
self.compiled[child]
.clone()
.unwrap_or_else(|| self.children[child].clone())
}
pub fn interned(spec: &str) -> &'static TileProgram {
use std::sync::RwLock;
static PROGRAMS: RwLock<Option<HashMap<String, usize>>> = RwLock::new(None);
let found = PROGRAMS
.read()
.unwrap()
.as_ref()
.and_then(|m| m.get(spec).copied());
if let Some(p) = found {
return unsafe { &*(p as *const TileProgram) };
}
let built = Box::new(Self::from_json(spec));
let mut guard = PROGRAMS.write().unwrap();
let map = guard.get_or_insert_with(HashMap::new);
if let Some(&p) = map.get(spec) {
return unsafe { &*(p as *const TileProgram) };
}
let leaked: &'static TileProgram = Box::leak(built);
map.insert(spec.to_string(), leaked as *const TileProgram as usize);
leaked
}
pub fn has_projections(&self) -> bool {
fn walk(ops: &[RtOp]) -> bool {
ops.iter().any(|op| match op {
RtOp::Repeat { .. } => true,
RtOp::Branch {
then, otherwise, ..
} => walk(then) || walk(otherwise),
_ => false,
})
}
walk(&self.ops)
}
pub fn render(&self, inputs: &[Value], bodies: &mut BodyKernels) -> String {
let refs: Vec<ValueRef<'_>> = inputs.iter().map(ValueRef::from).collect();
let mut out = String::new();
self.render_into(
&refs,
crate::Engine::Interpreter(crate::JitMode::Auto),
bodies,
&mut out,
);
out
}
pub fn render_into<W: std::fmt::Write>(
&self,
inputs: &[ValueRef<'_>],
engine: crate::Engine,
bodies: &mut BodyKernels,
out: &mut W,
) {
self.render_ops(&self.ops, inputs, engine, bodies, None, out);
}
fn render_ops<W: std::fmt::Write>(
&self,
ops: &[RtOp],
inputs: &[ValueRef<'_>],
engine: crate::Engine,
bodies: &mut BodyKernels,
mut child: Option<&mut BodyEntry>,
out: &mut W,
) {
for op in ops {
match op {
RtOp::Copy(s) => out.put(s),
RtOp::Hole(source, enc) => match source {
RtSource::Wire(i) => {
encode_ref(inputs.get(*i).copied().unwrap_or(ValueRef::None), enc, out)
}
RtSource::Child(name, k) => {
if let Some(entry) = child.as_deref_mut()
&& let Some(i) = entry.hole(*k, name)
{
let v = entry.kernel.pull_at(i);
encode_ref(ValueRef::from(&v), enc, out)
}
}
},
RtOp::Branch {
cond,
then,
otherwise,
} => {
let c = self.truthy(cond, inputs, child.as_deref_mut());
let branch = if c { then } else { otherwise };
self.render_ops(branch, inputs, engine, bodies, child.as_deref_mut(), out);
}
RtOp::Repeat {
stream,
child: child_idx,
sep,
body,
generators,
} => {
let memoized = self.memo[*child_idx].clone();
let tuples: std::borrow::Cow<'_, [RuntimeTuple]> = match &memoized {
Some(t) => std::borrow::Cow::Borrowed(&t[..]),
None => {
let mut streamer = (**stream).clone();
if !generators.is_empty() {
streamer.ast = bind_generators(&streamer.ast, generators, inputs);
}
std::borrow::Cow::Owned(
evaluate_for_iteration(
&streamer.ast,
&*self.canonicals[*child_idx],
&HashMap::new(),
|_| Ok(()),
)
.unwrap_or_else(|e| {
panic!(
"tile '{}': projection `for {}` failed at render: {e}",
self.spec.name, streamer.text
)
}),
)
}
};
let child_spec = &self.spec.children[*child_idx];
let engine = match engine {
crate::Engine::Interpreter(_) => engine,
_ => crate::Engine::default(),
};
let program = self.body_program_on(*child_idx, engine);
let mut first = true;
let fail = |name: &str, e: String| -> ! {
panic!(
"tile '{}': projection body input `{name}`: {e}",
self.spec.name
)
};
bodies.with(&program, engine, |entry, bodies| {
for (index, tuple) in tuples.iter().enumerate() {
if !first {
out.put(sep);
}
first = false;
{
let BodyEntry {
kernel,
elements,
cascade,
..
} = &mut *entry;
kernel.set_inputs(&[index as u64]);
let elements = elements.get_or_insert_with(|| {
tuple.iter().map(|(n, _)| kernel.input_index(n)).collect()
});
for (k, (name, v)) in tuple.iter().enumerate() {
if let Some(i) = elements.get(k).copied().flatten() {
kernel
.set_input_at(i, v.clone())
.unwrap_or_else(|e| fail(name, e));
}
}
let cascade = cascade.get_or_insert_with(|| {
child_spec
.cascade
.iter()
.map(|(n, _, _)| kernel.input_index(n))
.collect()
});
for (k, (name, input_idx, ty)) in
child_spec.cascade.iter().enumerate()
{
if let Some(i) = cascade.get(k).copied().flatten()
&& let Some(v) = inputs.get(*input_idx)
{
kernel
.set_input_at(i, typed_for(&owned(*v), ty))
.unwrap_or_else(|e| fail(name, e));
}
}
}
self.render_ops(body, inputs, engine, bodies, Some(entry), out);
}
});
}
}
}
}
fn truthy(
&self,
source: &RtSource,
inputs: &[ValueRef<'_>],
child: Option<&mut BodyEntry>,
) -> bool {
match source {
RtSource::Wire(i) => truthy_of(inputs.get(*i).copied().unwrap_or(ValueRef::None)),
RtSource::Child(name, k) => match child {
Some(entry) => match entry.hole(*k, name) {
Some(i) => truthy_of(ValueRef::from(&entry.kernel.pull_at(i))),
None => false,
},
None => false,
},
}
}
}
fn owned(v: ValueRef<'_>) -> Value {
match v {
ValueRef::U64(n) => Value::U64(n),
ValueRef::I64(n) => Value::I64(n),
ValueRef::F64(f) => Value::F64(f),
ValueRef::Bool(b) => Value::Bool(b),
ValueRef::Str(s) => Value::Str(Arc::from(s)),
ValueRef::Bytes(b) => Value::Bytes(Arc::from(b)),
ValueRef::Json(j) => Value::Json(Arc::new(j.clone())),
ValueRef::None => Value::None,
ValueRef::Other(v) => v.clone(),
}
}
fn typed_for(v: &Value, ty: &str) -> Value {
match (v, PortType::from_keyword(ty)) {
(Value::Str(_), Some(t)) if t != PortType::Str => retype(v, ty),
_ => v.clone(),
}
}
fn bind_generators(
c: &crate::iteration::comprehension::Comprehension,
generators: &[(String, usize, String)],
inputs: &[ValueRef<'_>],
) -> crate::iteration::comprehension::Comprehension {
use crate::iteration::comprehension::Comprehension as K;
use crate::iteration::comprehension::source::{LiteralValue, Source};
match c {
K::Clause {
name,
source: Source::Generator { .. },
} => {
let Some((_, idx, ty)) = generators.iter().find(|(n, _, _)| n == name) else {
return c.clone();
};
let raw = inputs.get(*idx).map(|v| owned(*v)).unwrap_or(Value::None);
let items: Vec<Value> =
match crate::iteration::comprehension::source_values::iteration_interior(&raw) {
Some(interior) => interior,
None => match &raw {
Value::Str(text) => {
match serde_json::from_str::<serde_json::Value>(text.trim()) {
Ok(serde_json::Value::Array(items)) => items
.iter()
.map(|j| {
retype(
&Value::Str(j.to_string().trim_matches('"').into()),
ty,
)
})
.collect(),
_ => vec![typed_for(&raw, ty)],
}
}
_ => vec![raw.clone()],
},
};
let json_items = ty == "json";
let values = items
.iter()
.map(|v| {
if json_items {
return LiteralValue::Json(json_of(v));
}
match v {
Value::U64(n) => LiteralValue::Int(*n as i64),
Value::I64(n) => LiteralValue::Int(*n),
Value::F64(f) => LiteralValue::Float(*f),
Value::Bool(b) => LiteralValue::Bool(*b),
Value::Json(j) => match j.as_ref() {
serde_json::Value::Number(n) if n.is_u64() => {
LiteralValue::Int(n.as_u64().unwrap_or(0) as i64)
}
serde_json::Value::Number(n) if n.is_i64() => {
LiteralValue::Int(n.as_i64().unwrap_or(0))
}
serde_json::Value::Number(n) => {
LiteralValue::Float(n.as_f64().unwrap_or(0.0))
}
serde_json::Value::Bool(b) => LiteralValue::Bool(*b),
serde_json::Value::String(s) => LiteralValue::String(s.clone()),
other => LiteralValue::String(other.to_string()),
},
other => LiteralValue::String(other.to_display_string()),
}
})
.collect();
K::Clause {
name: name.clone(),
source: Source::Literal { values },
}
}
K::Clause { .. } => c.clone(),
K::Cartesian { children } => K::Cartesian {
children: children
.iter()
.map(|ch| bind_generators(ch, generators, inputs))
.collect(),
},
K::Zip { children, mode } => K::Zip {
children: children
.iter()
.map(|ch| bind_generators(ch, generators, inputs))
.collect(),
mode: *mode,
},
K::Union { children } => K::Union {
children: children
.iter()
.map(|ch| bind_generators(ch, generators, inputs))
.collect(),
},
K::Filter { child, predicate } => K::Filter {
child: Box::new(bind_generators(child, generators, inputs)),
predicate: predicate.clone(),
},
K::Order {
child,
strategy,
truncation,
} => K::Order {
child: Box::new(bind_generators(child, generators, inputs)),
strategy: *strategy,
truncation: *truncation,
},
}
}
fn json_of(v: &Value) -> serde_json::Value {
match v {
Value::Json(j) => j.as_ref().clone(),
Value::U64(n) => serde_json::Value::from(*n),
Value::I64(n) => serde_json::Value::from(*n),
Value::F64(f) => serde_json::Number::from_f64(*f)
.map(serde_json::Value::Number)
.unwrap_or(serde_json::Value::Null),
Value::Bool(b) => serde_json::Value::Bool(*b),
Value::Str(s) => serde_json::Value::String(s.to_string()),
Value::None => serde_json::Value::Null,
other => serde_json::Value::String(other.to_display_string()),
}
}
fn retype(v: &Value, ty: &str) -> Value {
let text = v.to_display_string();
match PortType::from_keyword(ty) {
Some(PortType::U64) => text.parse().map(Value::U64).unwrap_or(Value::None),
Some(PortType::F64) => text.parse().map(Value::F64).unwrap_or(Value::None),
Some(PortType::Bool) => Value::Bool(matches!(text.trim(), "true" | "1")),
Some(PortType::Str) | None => Value::Str(text.into()),
Some(_) => v.clone(),
}
}
struct BodyEntry {
program: Arc<dyn KernelProgram>,
kernel: Box<dyn Kernel>,
elements: Option<Vec<Option<usize>>>,
cascade: Option<Vec<Option<usize>>>,
holes: Vec<Option<Option<usize>>>,
}
impl BodyEntry {
fn hole(&mut self, k: usize, name: &str) -> Option<usize> {
if self.holes.len() <= k {
self.holes.resize(k + 1, None);
}
if self.holes[k].is_none() {
self.holes[k] = Some(self.kernel.output_index(name));
}
self.holes[k].flatten()
}
}
#[derive(Default)]
pub struct BodyKernels {
entries: HashMap<(usize, crate::Engine), BodyEntry>,
created: u64,
}
impl Clone for BodyKernels {
fn clone(&self) -> Self {
Self::default()
}
}
unsafe impl Sync for BodyKernels {}
impl std::fmt::Debug for BodyKernels {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BodyKernels")
.field("entries", &self.entries.len())
.field("created", &self.created)
.finish()
}
}
fn body_engine_key(engine: crate::Engine) -> crate::Engine {
match engine {
crate::Engine::Interpreter(_) => crate::Engine::Interpreter(crate::JitMode::Auto),
other => other,
}
}
impl BodyKernels {
pub fn created(&self) -> u64 {
self.created
}
#[cfg(test)]
fn clone_for_test(&self) -> (u64, BodyKernels) {
(self.created, self.clone())
}
fn with(
&mut self,
program: &Arc<dyn KernelProgram>,
engine: crate::Engine,
f: impl FnOnce(&mut BodyEntry, &mut BodyKernels),
) {
let engine = body_engine_key(engine);
let key = (Arc::as_ptr(program) as *const () as usize, engine);
let mut entry = self
.entries
.remove(&key)
.filter(|e| Arc::ptr_eq(&e.program, program))
.unwrap_or_else(|| {
self.created += 1;
BodyEntry {
program: program.clone(),
kernel: program.clone().create_kernel(),
elements: None,
cascade: None,
holes: Vec::new(),
}
});
f(&mut entry, self);
self.entries.insert(key, entry);
}
}
pub(crate) mod render_state {
use super::{BodyKernels, TileRender};
use crate::ast::{ScratchBuf, ScratchElem, Value};
pub(crate) fn layout(_node: &TileRender) -> Vec<ScratchElem> {
vec![ScratchElem::Kernels]
}
pub(crate) fn eval(
node: &TileRender,
scratch: &mut [ScratchBuf],
inputs: &[Value],
outputs: &mut [Value],
) {
let bodies = bodies_of(&mut scratch[0]);
outputs[0] = Value::Str(node.program.render(inputs, bodies).into());
}
pub(crate) fn bodies_of(entry: &mut ScratchBuf) -> &mut BodyKernels {
match entry {
ScratchBuf::Kernels(b) => b,
other => panic!("a tile render's scratch holds {other:?}, not its body kernels"),
}
}
}
pub(crate) trait Sink: std::fmt::Write {
fn put(&mut self, s: &str) {
let _ = self.write_str(s);
}
fn put_char(&mut self, c: char) {
let _ = self.write_char(c);
}
}
impl<W: std::fmt::Write> Sink for W {}
pub fn encode<W: std::fmt::Write>(value: &Value, enc: &HoleEncoding, out: &mut W) {
encode_ref(ValueRef::from(value), enc, out)
}
pub fn encode_ref<W: std::fmt::Write>(value: ValueRef<'_>, enc: &HoleEncoding, out: &mut W) {
if enc.cond {
out.put_char(if truthy_of(value) { '1' } else { '0' });
return;
}
let ty = enc.ty.as_deref();
if is_numeric_keyword(ty.unwrap_or("u64")) {
match (enc.format.as_deref(), value) {
(None, ValueRef::U64(n)) => {
put_u64(n, out);
return;
}
(None, ValueRef::I64(n)) => {
if n < 0 {
out.put_char('-');
}
put_u64(n.unsigned_abs(), out);
return;
}
(None, ValueRef::F64(f)) => {
let _ = float_text::write_shortest(f, out);
return;
}
(Some(fmt), ValueRef::F64(_) | ValueRef::U64(_)) => {
if let (Some(prec), Some(f)) = (precision_of(fmt), as_f64(value)) {
let _ = float_text::write_fixed(f, prec, out);
return;
}
}
_ => {}
}
}
let text = formatted_text(value, ty, enc.format.as_deref());
if enc.raw {
out.put(&text);
return;
}
match (enc.encoding.as_str(), enc.position) {
("json", HolePosition::InString) => push_json_escaped(&text, out),
("json", HolePosition::Value) => {
let kind = ty.unwrap_or_else(|| value.port_type().to_keyword());
match (kind, value) {
(_, ValueRef::None) => out.put("null"),
("bool", _) => out.put(if truthy_of(value) { "true" } else { "false" }),
("json", ValueRef::Json(j)) => {
let _ = write!(out, "{j}");
}
("str", _) | ("String", _) | ("string", _) => {
out.put_char('"');
push_json_escaped(&text, out);
out.put_char('"');
}
(k, _) if is_numeric_keyword(k) => out.put(&text),
(_, ValueRef::Json(j)) => {
let _ = write!(out, "{j}");
}
(_, ValueRef::Bool(b)) => out.put(if b { "true" } else { "false" }),
(_, ValueRef::U64(_)) | (_, ValueRef::F64(_)) => out.put(&text),
_ => {
out.put_char('"');
push_json_escaped(&text, out);
out.put_char('"');
}
}
}
("csv", _) => {
if text.contains([',', '"', '\n']) {
out.put_char('"');
for (i, piece) in text.split('"').enumerate() {
if i > 0 {
out.put("\"\"");
}
out.put(piece);
}
out.put_char('"');
} else {
out.put(&text);
}
}
_ => out.put(&text),
}
}
fn put_u64<W: std::fmt::Write>(mut n: u64, out: &mut W) {
if n == 0 {
out.put_char('0');
return;
}
let mut buf = [0u8; 20];
let mut i = buf.len();
while n > 0 {
i -= 1;
buf[i] = b'0' + (n % 10) as u8;
n /= 10;
}
out.put(std::str::from_utf8(&buf[i..]).expect("ascii digits"));
}
fn truthy_of(v: ValueRef<'_>) -> bool {
match v {
ValueRef::Bool(b) => b,
ValueRef::U64(n) => n != 0,
ValueRef::F64(f) => f != 0.0,
ValueRef::Str(s) => !s.is_empty() && s != "0" && s != "false",
ValueRef::None => false,
_ => true,
}
}
fn is_numeric_keyword(k: &str) -> bool {
matches!(
k,
"u64"
| "i64"
| "f64"
| "f32"
| "u32"
| "i32"
| "u16"
| "i16"
| "u8"
| "i8"
| "u128"
| "i128"
| "f16"
)
}
fn formatted_text<'a>(
value: ValueRef<'a>,
ty: Option<&str>,
format: Option<&str>,
) -> std::borrow::Cow<'a, str> {
use std::borrow::Cow;
let base = |value: ValueRef<'a>| -> Cow<'a, str> {
match (ty, value) {
(Some("bool"), v) => Cow::Owned(truthy_of(v).to_string()),
(_, ValueRef::Json(serde_json::Value::String(s))) => Cow::Owned(s.clone()),
(_, ValueRef::Json(j)) => Cow::Owned(j.to_string()),
(_, v) => v.display(),
}
};
let Some(fmt) = format else {
return base(value);
};
let fmt = fmt.trim();
if let Some(prec) = precision_of(fmt) {
if let Some(f) = as_f64(value) {
return Cow::Owned(float_text::fixed_string(f, prec));
}
return base(value);
}
if fmt == "x" || fmt == "X" {
if let ValueRef::U64(n) = value {
return Cow::Owned(if fmt == "x" {
format!("{n:x}")
} else {
format!("{n:X}")
});
}
return base(value);
}
let base = base(value);
if let Some(w) = fmt.strip_prefix('0').and_then(|w| w.parse::<usize>().ok()) {
return Cow::Owned(format!("{base:0>w$}"));
}
if let Some(w) = fmt.strip_prefix('>').and_then(|w| w.parse::<usize>().ok()) {
return Cow::Owned(format!("{base:>w$}"));
}
if let Some(w) = fmt.strip_prefix('<').and_then(|w| w.parse::<usize>().ok()) {
return Cow::Owned(format!("{base:<w$}"));
}
if let Ok(w) = fmt.parse::<usize>() {
return Cow::Owned(format!("{base:>w$}"));
}
base
}
fn as_f64(v: ValueRef<'_>) -> Option<f64> {
match v {
ValueRef::F64(f) => Some(f),
ValueRef::U64(n) => Some(n as f64),
_ => None,
}
}
fn precision_of(fmt: &str) -> Option<usize> {
fmt.trim()
.strip_prefix('.')
.and_then(|p| p.parse::<usize>().ok())
}
fn push_json_escaped<W: std::fmt::Write>(s: &str, out: &mut W) {
for c in s.chars() {
match c {
'"' => out.put("\\\""),
'\\' => out.put("\\\\"),
'\n' => out.put("\\n"),
'\r' => out.put("\\r"),
'\t' => out.put("\\t"),
c if (c as u32) < 0x20 => {
let _ = write!(out, "\\u{:04x}", c as u32);
}
c => out.put_char(c),
}
}
}
#[crate::polydat_node(category = Formatting)]
fn tile_encode(
value: Value,
spec: Const<&str>,
#[poly_const(HoleEncoding::from_spec, from = spec)] enc: &HoleEncoding,
) -> String {
let mut out = String::new();
encode(&value, enc, &mut out);
out
}
fn tile_render_compiled(node: &TileRender, wire_types: &[PortType]) -> crate::ast::CompiledSlotKit {
let program: &'static TileProgram = TileProgram::interned(&node.spec);
let mut reads: Vec<(usize, PortType)> = Vec::with_capacity(wire_types.len());
let mut offset = 0usize;
for &ty in wire_types {
reads.push((offset, ty));
offset += ty.slot_width().max(1);
}
crate::ast::CompiledSlotKit {
scratch: vec![
crate::ast::ScratchElem::Str,
crate::ast::ScratchElem::Kernels,
],
op: Box::new(
move |inputs: &[u64], outputs: &mut [u64], scratch: &mut [crate::ast::ScratchBuf]| {
let owned_values: Vec<Value> = reads
.iter()
.filter(|(_, ty)| ty.slot_color() == crate::ast::SlotColor::Imm2)
.map(|&(offset, ty)| crate::compile::marshal::decode_output(inputs, offset, ty))
.collect();
let mut next_owned = 0usize;
let refs: Vec<ValueRef<'_>> = reads
.iter()
.map(|&(offset, ty)| {
if ty.slot_color() == crate::ast::SlotColor::Imm2 {
let v = ValueRef::from(&owned_values[next_owned]);
next_owned += 1;
v
} else {
unsafe { crate::compile::marshal::arg_ref(ty, &inputs[offset..]) }
}
})
.collect();
let (text, bodies) = scratch.split_at_mut(1);
let crate::ast::ScratchBuf::Str(buf) = &mut text[0] else {
unreachable!("the render step owns a string entry");
};
let bodies = render_state::bodies_of(&mut bodies[0]);
buf.clear();
let mut w = BytesSink(buf);
program.render_into(&refs, crate::Engine::default(), bodies, &mut w);
let (p, l) = scratch[0].ptr_len();
outputs[0] = p;
outputs[1] = l;
},
),
}
}
pub(crate) struct BytesSink<'a>(pub(crate) &'a mut Vec<u8>);
impl std::fmt::Write for BytesSink<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
self.0.extend_from_slice(s.as_bytes());
Ok(())
}
}
#[crate::polydat_node(
category = Formatting,
variadic_min = 0,
compiled_slot = tile_render_compiled,
state = render_state
)]
fn tile_render(
spec: Const<&str>,
#[poly_const(TileProgram::from_json, from = spec)] program: &TileProgram,
values: &[Value],
) -> String {
program.render(values, &mut BodyKernels::default())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn body_kernels_are_created_once_per_state_and_reused() {
let src =
"input cycle: u64\ntile t : text := \"@for k in 0..3 sep \\\",\\\" {${k + cycle}}\"\n";
let mut k = crate::dsl::compile_polydat(src).unwrap();
let program = k.program();
let node = (0..program.node_count())
.find(|&i| program.node_meta(i).name == "tile_render")
.expect("the tile's render node");
let bodies_of = |k: &mut PolydatKernel| match &k.state().core.node_scratch[node][0] {
crate::ast::ScratchBuf::Kernels(b) => b.clone_for_test(),
other => panic!("{other:?}"),
};
assert_eq!(bodies_of(&mut k).0, 0, "nothing before the first render");
k.set_inputs(&[10]);
assert_eq!(k.pull("t").as_str(), "10,11,12");
assert_eq!(bodies_of(&mut k).0, 1, "one kernel for the body");
for c in 0..5u64 {
k.set_inputs(&[c]);
let _ = k.pull("t");
}
let (created, clone) = bodies_of(&mut k);
assert_eq!(created, 1, "reused across renders");
assert_eq!(clone.created(), 0, "a clone is a new state's empty set");
}
fn enc(
encoding: &str,
position: HolePosition,
ty: Option<&str>,
format: Option<&str>,
raw: bool,
) -> HoleEncoding {
HoleEncoding {
encoding: encoding.into(),
position,
ty: ty.map(str::to_string),
format: format.map(str::to_string),
raw,
cond: false,
}
}
#[test]
fn json_value_and_string_positions_encode_by_type() {
let mut out = String::new();
encode(
&Value::Str("a\"b".into()),
&enc("json", HolePosition::Value, Some("str"), None, false),
&mut out,
);
assert_eq!(out, "\"a\\\"b\"");
out.clear();
encode(
&Value::U64(7),
&enc("json", HolePosition::Value, None, None, false),
&mut out,
);
assert_eq!(out, "7");
out.clear();
encode(
&Value::Str("x\ny".into()),
&enc("json", HolePosition::InString, None, None, false),
&mut out,
);
assert_eq!(out, "x\\ny");
out.clear();
encode(
&Value::F64(2.0 / 3.0),
&enc("json", HolePosition::Value, None, Some(".2"), false),
&mut out,
);
assert_eq!(out, "0.67");
out.clear();
encode(
&Value::None,
&enc("json", HolePosition::Value, None, None, false),
&mut out,
);
assert_eq!(out, "null");
}
#[test]
fn spec_round_trips() {
let e = enc(
"json",
HolePosition::InString,
Some("u64"),
Some(".2"),
true,
);
assert_eq!(HoleEncoding::from_spec(&e.to_spec()), e);
let c = HoleEncoding {
cond: true,
..enc("text", HolePosition::Text, None, None, false)
};
assert_eq!(HoleEncoding::from_spec(&c.to_spec()), c);
}
#[test]
fn csv_quotes_when_needed_and_raw_skips_escaping() {
let mut out = String::new();
encode(
&Value::Str("a,b".into()),
&enc("csv", HolePosition::Text, None, None, false),
&mut out,
);
assert_eq!(out, "\"a,b\"");
out.clear();
encode(
&Value::Str("a\"b".into()),
&enc("json", HolePosition::Value, None, None, true),
&mut out,
);
assert_eq!(out, "a\"b");
}
#[test]
fn formats_apply_before_encoding() {
assert_eq!(formatted_text(ValueRef::U64(5), None, Some("03")), "005");
assert_eq!(formatted_text(ValueRef::U64(255), None, Some("x")), "ff");
assert_eq!(
formatted_text(ValueRef::Str("ab"), None, Some(">4")),
" ab"
);
assert_eq!(
formatted_text(ValueRef::F64(0.295), None, Some(".2")),
"0.29"
);
assert_eq!(
formatted_text(ValueRef::U64(7), None, Some(" .3 ")),
"7.000"
);
}
#[test]
fn float_holes_write_rust_text() {
let cases: [(f64, Option<&str>, &str); 8] = [
(100.0, None, "100.0"),
(0.1, None, "0.1"),
(5e-5, None, "5e-5"),
(1e16, None, "1e16"),
(-0.0, None, "-0.0"),
(2.0 / 3.0, Some(".2"), "0.67"),
(0.295, Some(".2"), "0.29"),
(2.5, Some(".0"), "2"),
];
for (f, fmt, want) in cases {
for (encoding, position) in [
("text", HolePosition::Text),
("json", HolePosition::Value),
("json", HolePosition::InString),
("csv", HolePosition::Text),
] {
for ty in [None, Some("f64")] {
let mut out = String::new();
encode(
&Value::F64(f),
&enc(encoding, position, ty, fmt, false),
&mut out,
);
assert_eq!(out, want, "{f:?} {fmt:?} {encoding} {position:?} {ty:?}");
}
}
let mut out = String::new();
encode(
&Value::F64(f),
&enc("json", HolePosition::Value, Some("str"), fmt, false),
&mut out,
);
assert_eq!(out, format!("\"{want}\""));
}
}
}