polydat 0.3.2

Polydat — a variates construction engine
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
# Polytile — Compiled Variate Templates

**Ownership:** Polydat owns the tile grammar in both its textual and
structural forms, the skeleton IR, the type model for holes, the
renderers at every engine level, and the encodings named here. Hosts own
what they do with a rendered tile and which containment form they hand
Polydat.

**Companion documents:**
[The `for` Construct](for_traversal.md) (producers, traversal, activation),
[Language Spec](language_spec.md) (string interpolation, expressions),
[Type System](type_system.md) and [Type-System Alignment](type_system_alignment.md)
(`Str`, `Bytes`, `Json`, adapters),
[Engines](engines.md) and [JIT Boundary](jit_boundary.md) (P1, P2, P3),
[Compiled By-Reference Slots](compiled_handles.md) (strings and
documents in compiled slots). The walk-through with real output is
[the Polytile tutorial](../tutorials/polytile_tutorial.md).

## 1. The claim

Polydat produces variates. Most of what consumes them wants an encoding:
a CQL statement, a JSON document, a CSV row, a protobuf message. Without
tiles that encoding is assembled from pieces. Flat text goes through string
interpolation, which is `printf` over segments. JSON goes through
`json_object`, `json_array`, and `to_json`, which build a `serde_json`
value per cycle and serialize it. A document with a deep static skeleton
and a few dynamic leaves is rebuilt and re-serialized in full on every
cycle, and every nested object costs an allocation.

Polytile is a template language for that last step. A tile is a skeleton
of static bytes with typed holes bound to wires. The skeleton is fixed at
compile time, including every nested arm that contains no hole, so
rendering copies static byte ranges and encodes hole values, nothing
more. A tile is a wire like any other, so it composes with the rest of
the graph, participates in lifecycle classification, and rides the
compiled tiers as the reference pair that carries every string.
Projections over comprehensions are part of the grammar, so a
tile can repeat a sub-skeleton over a producer or an inline
comprehension.

A tile has to live where templates actually live: inside a YAML workload,
inside a JSON body in a config file, inside a statement string that
another tool also templates, or as a data structure rather than text at
all. So the grammar has two front ends, textual and structural, and every
form is chosen so it can be carried as an ordinary string or an ordinary
document by whatever contains it.

Three limits shape the design, the same three that shape the kernel:

- **Structure lives in the template, not in the data.** However deep a
  document is, its static arms are serialized once at compile time and
  copied by range at render time. Adding a static field costs bytes, not
  work.
- **Rendering cost is the cost of the output.** Bytes copied plus holes
  encoded plus projection tuples times body cost. No intermediate tree.
- **A tile is a pure function of its coordinate.** Same inputs, same
  bytes, on every engine and every host.

## 2. The textual form

### 2.1 Statement grammar

Extends [Grammar](grammar.md) §2.

```ebnf
statement   ::= ...existing...
             |  tile_def

tile_def    ::= "tile" ident (":" encoding)? options? ":=" tile_body
encoding    ::= "json" | "text" | "csv"                (* extensible; default text *)
options     ::= "(" option ("," option)* ")"
option      ::= "delims" string string                  (* hole delimiters *)
             |  "sigil" string                          (* directive prefix *)
             |  "strict"
             |  "instring"

tile_body   ::= json_block                              (* json: balanced { } or [ ] *)
             |  heredoc                                 (* <<< ... >>> *)
             |  string_literal                          (* one-line tiles *)
```

A tile statement has the shape of every other wire binding in the
grammar, `modifier name : type := value`: `tile` is the modifier, the
encoding is the type of the document that flows on the wire (its port
type is `Str`), and `:=` binds the wire, so `${doc!}` in another tile
and `f(doc)` in a binding read it as they read any wire. The encoding
defaults to `text`, so `tile greeting := "..."` is the whole statement
for a one-line text tile. `:=` is mandatory in every body form, and a
header without it is an error naming the tile, because a tile is a wire
binding like every other: `:=` assigns a wire that can be named
symbolically and wired wherever a wire is accepted, which neither `:`
nor `=` means.

The lexer captures a tile body raw after `:=`, as it does the text
after `for`. A `json` body is a brace- or bracket-balanced block,
string-aware, so the template is written in JSON's own syntax. A heredoc
body is everything between `<<<` and `>>>` and suits any encoding. A
string-literal body is an ordinary Polydat string and suits short tiles.

### 2.2 Template grammar

Inside a body:

```ebnf
hole        ::= open expr (":" type)? ("|" format)? ("!")? close
projection  ::= sigil "for" for_source ("sep" string)? "{" body "}"
branch      ::= sigil "if" expr "{" body "}" (sigil "else" "{" body "}")?
splice      ::= open tile_name close
escape      ::= open open                               (* a literal open delimiter *)
```

`open` and `close` default to `${` and `}`; `sigil` defaults to `@`.
Both are overridable per tile (§2.3) and per program by a host
(`apply_tile_defaults`, §5.6).

- A **hole** is a Polydat expression in the enclosing scope. `:type` is a
  declared type (§4). `|format` is a printf format spec applied before
  encoding. `!` marks the hole raw: its text is copied without the
  encoding's escaping. Polydat's own `{name}` interpolation stays
  available inside string literals within a hole expression.
- A **projection** repeats its body once per tuple of a comprehension.
  `for_source` is the same surface as the `for` construct: inline
  comprehension text or a bound producer, including derivations. Element
  names are wires inside the body. `sep` overrides the encoding's
  default separator between repetitions.
- A **branch** renders one of two bodies by a `u64` condition. Both
  bodies must satisfy the encoding's structural rules.
- A **splice** is a hole whose expression is the bare name of another
  tile in scope. It is resolved at compile time by inlining that tile's
  skeleton.
- A doubled open delimiter is a literal open delimiter.
- The braces of a directive block delimit it; whitespace padding them
  is not body. `@if x { "hot" }` renders `"hot"`. Braces inside body
  text are balanced, so JSON objects sit in a block unescaped.
- The `instring` option declares that the body begins inside a JSON
  string literal, so holes encode as escaped text from the first byte.
  The compiler sets it on the tile it makes for a projection nested in
  a string position; authors rarely need it.
- A block body (`{ ... }` or `[ ... ]` after the header) keeps the
  author's layout but not the indentation of the statement around it:
  the common leading whitespace of the lines after the first is
  removed. A tile inside a `for` body therefore renders the same bytes
  as the same tile at top level. Heredoc and string bodies are exact.
- A hole cannot appear in a directive header. Polydat's `{name}`
  interpolation may, as in `where {k} > 0`; a free-standing `{word}`
  after the header is the block itself (`@if x {plain}`), and `{name}`
  is read as interpolation only when it is attached to header
  punctuation (`1..{n}`) or followed by an operator.

Examples:

```text
tile reading : json := {
    "meta": { "schema": 3, "source": "polydat", "units": { "temp": "C", "rh": "%" } },
    "tenant": ${tenant_id},
    "device": "${device_id}",
    "ts": ${ts: u64},
    "samples": [ @for s in 0..4 { { "n": ${s}, "temp": ${temp_c + s | .2} } } ],
    "status": "${status}"
}

tile load := <<<
INSERT INTO ${keyspace}.${table} (tenant_id, device_id, ts, doc)
VALUES (${tenant_id}, '${device_id}', ${ts}, '${reading!}')
>>>

tile row : csv := "${tenant_id},${device_id},${ts},${status}"
```

`tile` is a hard keyword. Directives and delimiters have no meaning
outside a tile body.

### 2.3 Containment: living inside another grammar

A tile is often carried by something that has its own template syntax or
its own idea of what braces, dollars, and at-signs mean: a YAML workload
whose values are strings, a JSON config, a shell here-doc, a CQL string
that another tool also expands, a Jinja or Handlebars page. Four rules
make a tile portable into those places.

1. **Delimiters are declarable.** `tile t (delims "<%" "%>")`
   uses `<%expr%>` for holes; `(sigil "#")` uses `#for` and `#if`. A
   host may also set defaults for every tile in a program it compiles
   (`apply_tile_defaults`, §5.6). The
   canonical defaults are `${`, `}`, and `@`, chosen because they are
   inert in JSON, CQL, SQL, YAML double-quoted strings, and Markdown, and
   because `{name}` interpolation inside Polydat strings is untouched.
2. **Every body form is carryable as a string.** The heredoc and
   string-literal bodies contain no construct that a YAML or JSON string
   cannot hold. Where the carrier's own escaping interferes, the doubled
   open delimiter (`${${`) is the only escape a tile needs, and a host
   can choose delimiters the carrier never uses.
3. **The tile keyword is optional at the host boundary.** A host that
   holds only a string can hand it to Polydat as a tile without wrapping
   it in a statement: the `polytile(encoding, text)` binding form and
   the `tile_from_text` API (§5.6) accept bare template text, so a YAML value
   `body: '{"tenant": ${tenant_id}}'` becomes a tile with no grammar the
   YAML author has to learn beyond the hole syntax.
4. **Nested carriers compose by encoding, not by text.** A tile that is
   itself the value of a hole in another tile is spliced (compile time)
   or rendered and copied (raw hole), never re-parsed. A tile carried by
   another template engine is opaque text to that engine as long as the
   two do not share delimiters, which rule 1 guarantees the author can
   arrange.

A tile body never needs a construct outside its own hole and directive
syntax, so no carrier ever has to understand Polydat to carry one.

## 3. The structural form

A template can be a document rather than text: a JSON value (or a YAML
value, which is the same thing once loaded) in which strings define
insertion points. This is the natural form for hosts whose configuration
is already structured, and it is how a workload file can carry a
document template as data.

### 3.1 Insertion points

In a structural template, a string is examined for holes:

| String value | Meaning |
| --- | --- |
| exactly one hole, `"${expr}"` | a **value hole**: the string node is replaced by the hole's value, encoded by type. `"${ts}"` renders as `1700000000000`, a bare number. |
| text with one or more holes, `"row-${row}"` | a **string hole**: renders as a string with the holes encoded as text inside it. |
| exactly one hole with a `str` declaration, `"${ts: str}"` | a string hole whose value is the number's text; the declaration forces string position. |
| a directive string, `"@for s in 0..4"` or `"@if cond"` | a **structural directive**; see §3.2. |
| no hole | static content, folded into the skeleton. |

The distinction between a value hole and a string hole is the one that
lets a structural template express `"ts": 1700000000000` and
`"device": "d9ac..."` from the same string syntax: the wire's type
decides, and a declaration overrides.

### 3.2 Directives in structure

Projections and branches are expressed with the carrier's own array and
object shapes:

```json
{
  "meta": { "schema": 3, "units": { "temp": "C" } },
  "tenant": "${tenant_id}",
  "samples": [ "@for s in 0..4", { "n": "${s}", "temp": "${temp_c + s | .2}" } ],
  "audit": [ "@if verbose", { "by": "${operator}" } ],
  "tags": { "@for t in tags": { "${t}": true } }
}
```

- An **array** whose first element is a `@for` string is a projection:
  the remaining elements form the body, rendered per tuple and separated
  as array items. With one body element the projection yields that
  element per tuple; with several, each tuple contributes all of them in
  order.
- An **array** whose first element is an `@if` string is a branch: the
  remaining elements render when the condition holds; a following
  `"@else"` string separates the alternative.
- An **object** with a single `@for` key is a member projection: the
  value is an object template rendered per tuple, and its members are
  merged into the enclosing object. A key that is itself a hole is
  rendered as the member name.

Arrays and objects without a leading directive are static structure with
holes inside.

**Structural commas.** A directive member beside static members carries
its own separating comma inside each repetition, leading or trailing as
its position requires, so a projection that renders zero tuples or a
branch that renders nothing leaves the object valid without a
cardinality check.

### 3.3 One skeleton

The structural front end produces the same skeleton IR as the textual
front end (§6). Static arms of the document, however deep, fold into
single byte ranges exactly as in the textual form; the only difference
is that the structural form is validated by construction rather than by
parsing the template with placeholders.

A host may hand Polydat a `serde_json::Value` directly through
`tile_from_json_value` (§5.6) and compile it with
`compile_polydat_with_tiles`, or embed the document in a Polydat file as a
`json` tile body, which is the textual form of the same thing. The two
forms are interconvertible: a structural template pretty-prints as a
valid textual `json` tile, and a textual `json` tile parses to the same
structure.

## 4. Type awareness

Every hole has a type, known at compile time, and the encoder for a hole
is chosen by that type. The type comes from three sources in priority
order.

### 4.1 Declared type

`${expr: u64}` declares the hole's type. The compiler inserts the same
adapter it would insert for a wire of the expression's type feeding a
port of the declared type: lossless widening is automatic, narrowing and
string-to-number conversions must be written explicitly, and an
impossible conversion is a compile error at the hole's position. The
declaration is the author's statement of what the encoding should see,
and it wins over everything else.

Type keywords are the port-type keywords of the type system: `u64`,
`i64`, `f64`, `str`, `bool`, `json`, `bytes`, and the rest.

### 4.2 Wire type

Without a declaration, the hole takes the compile-time type of its
expression, resolved by the same inference the compiler applies to any
binding: literals, declared inputs and externs, node return types, and
`for` elements all carry types. This covers most holes, and it is what
lets `"${ts}"` in a structural template become a number.

### 4.3 Contextual type

Where an encoding assigns a meaning to a position, that position carries
an expected type, and the hole is checked against it:

| Encoding | Position | Expected | Rule |
| --- | --- | --- | --- |
| `json` | inside a string literal | text | any type renders as text, escaped |
| `json` | value position | any JSON value | the wire's type picks the JSON form; `Str` is quoted, numbers bare, `Bool` bare, `Json` serialized, `None` is `null` |
| `json` | object key | text | any type renders as text, escaped |
| `csv` | field | text | any type renders as text, quoted when needed |
| `text` | anywhere | text | display form |

Contextual expectations never silently change a value's meaning. A `Str`
wire at a JSON value position stays a string; if the author wants a
number there, the declaration `: u64` says so and the conversion is
explicit. A `Json` wire inside a string literal is serialized and
escaped, not spliced.

### 4.4 Diagnostics and strict mode

A hole whose declared type cannot be reached from its wire type, or whose
wire type is unknown, is a compile error naming the tile, the hole's
position in the template, the expression, and both types. Under
`(strict)` or the compiler's strict mode, implicit adapters at holes are
rejected exactly as implicit adapters on wires are, so every conversion
is written down.

`explain tiles` prints each hole with its expression, its wire type, its
declared type if any, its contextual expectation, and the encoder that
was chosen, so the typing of a document is inspectable before it runs.

## 5. Semantics

### 5.1 A tile is a wire

A tile binds a wire named by its definition. Its port type is `Str`; a
future binary encoding (§9) would bind `Bytes`. Its value is the byte
sequence obtained by substituting each hole's encoded text into the
template, in order. Its lifecycle follows its holes: a tile whose holes
are all const is const, and any dynamic hole makes it dynamic. A tile
with no holes is a constant and folds like one.

A tile depends exactly on the wires its holes and projections reference,
so provenance and invalidation treat it like any other node. Pulling a
tile evaluates only the holes that changed.

### 5.2 Encodings

An encoding defines how a body is captured, how each hole is encoded by
type, what structural rules the skeleton must satisfy, and the default
separator for projections.

| Encoding | Capture | Structural rules | Separator |
| --- | --- | --- | --- |
| `json` | balanced block, heredoc, string, or structural document | the skeleton with holes at value positions is valid JSON; `@for` in an array repeats items; `@for` in an object repeats members | `,` |
| `text` | heredoc or string | none | none |
| `csv` | heredoc or string | one record per render; fields separated by the delimiter | the delimiter |

A `json` tile is checked at compile time: the skeleton with every hole
replaced by the placeholder `0`, each projection body written once, and
each branch showing its first arm must parse as JSON. `0` is a value in
value position and text inside a string or a key, so the check accepts
every well-placed hole and rejects a missing comma, an unquoted key, or
a hole where no value may go. A tile that fails is a compile error
naming the tile, serde's position, and the placeholder skeleton.
Author tiles are checked; the body tiles the compiler makes for nested
projections are fragments and are not. An `instring` tile begins inside
a string literal and is not a document, so it is not checked either.

Raw holes bypass the encoding's escaping. They exist for splicing
pre-encoded content, such as one tile's rendered bytes into another at
render time when compile-time splicing is not possible, and they are the
author's responsibility.

### 5.3 Holes

A hole's expression is compiled as a binding in the enclosing scope, so
it sees every wire the scope sees, including `for` elements and cascaded
outer wires when the tile is declared inside a traversal body. The
expression's type decides the encoder per §4. A format spec applies
first, then the encoder, then the raw flag decides whether escaping
applies.

### 5.4 Projections

A projection's comprehension must have bounded cardinality; an unbounded
source is a compile error. The body's holes may reference the
comprehension's element names and any wire of the enclosing scope. The
body renders once per tuple with the elements bound, separated by the
encoding's default separator or `sep`. Element names are typed from the
comprehension's sources by the same table as [The `for`
Construct](for_traversal.md) §3.3.

A body may contain further projections; each nests as one compiled
program per lexical position, exactly as the `for` construct nests
bodies, and reads outer elements and scope wires alike. Sources resolve
through the `for` construct's resolver and tuples are evaluated with the
`for` runtime's evaluator, so order strategies, truncation, and
predicates behave exactly as in `for`. A generator-call source is an
expression over the enclosing scope: it compiles as a wire there, so it
may read any wire in scope, and its value is bound into the
comprehension as the clause's element list, one tuple for a scalar and
one per item for a list; a projection therefore never needs a kernel of
its own to evaluate its sources. The body's own input is the tuple
index; the enclosing program's `cycle` cascades into the body like any
other outer wire. Render-node inputs cross into the body as typed
values, not display text. A filter predicate sees the comprehension's
elements; a `{name}` that names a wire outside it is a compile error, as
in `for`. A continuous source has no finite tuple set of its own; it
projects when its order names a sampling strategy (`halton`, `sobol`,
`lhs`, `shuffle`) with a count, which samples that many points from its
intervals, for tiles and `for` alike.

A projection over a bound producer dispenses the producer's stream at
render time. Two renders of the same tile never share dispense state.

### 5.5 Splicing

`${name}` where `name` is a tile in scope splices that tile's skeleton
into this one at compile time. Its holes join this tile's holes; its
static bytes join this tile's static bytes; adjacent statics coalesce.
Splicing is transitive and must be acyclic. Only a tile of the same
encoding is spliced; across encodings the named tile is an ordinary
wire whose rendered text enters through the hole and is encoded by the
outer tile's rules, or inlined with `!`. A splice belongs where a value
belongs: a `json` tile spliced inside a string literal of another
`json` tile inlines its skeleton there unescaped, and the compile-time
check of §5.2 rejects the result. To carry a same-encoding tile's text
inside a string, bind it first (`s := inner`) and use the binding,
which is an ordinary string wire. A `json` document carried in a
`text` statement is `'${doc!}'`; a `text` message carried in a `json`
document is `"body": ${msg}` and arrives quoted and escaped.

### 5.6 Host APIs

Beyond the statement form, hosts build tiles from what they hold:

```text
tile_from_text(name, encoding, text, options, span)   textual body, bare
tile_from_json_text(name, json, options, span)        structural body as text
tile_from_json_value(name, value, options, span)      structural body, already parsed
compile_polydat_with_tiles(source, tiles)             compile them with a program

name := polytile(encoding, body, options...)    in source; body is a string or heredoc
name := polytile_json(body, options...)         in source; structural JSON
```

The Rust functions live in `polydat::tile`; the three `tile_from_*`
functions each return `Result<TileDef, String>`, the `TileDef` the
`tile` keyword produces. `polytile` and `polytile_json`
are binding forms the parser rewrites into `tile` statements, so a host
that only has strings, such as a YAML workload runner, lowers
`body: '{"tenant": ${tenant_id}}'` to `doc := polytile("json", "...")`
as a program transform and never touches a runtime decorator. The body
is taken raw, never evaluated. Options `open`, `close`, `sigil`,
`strict`, and `instring` are named arguments; a host gives every tile
in a program its own delimiters with the `apply_tile_defaults` program
transform before compiling.

## 6. Compilation

A tile compiles to a **skeleton**: a straight-line program over a small
instruction set.

```text
Copy    { static: handle, range }           copy bytes from the static interner
Hole    { wire, encoder, format }           encode a wire's value
Repeat  { stream, body: skeleton, sep }     render body per tuple
Branch  { cond, then: skeleton, else }      render one body
```

Compilation proceeds in six passes:

1. **Parse** the body into segments, holes, projections, and branches,
   with template positions for diagnostics. The textual front end
   tokenizes by delimiter; the structural front end walks the document
   and classifies strings per §3.1.
2. **Splice** referenced tiles, checking for cycles.
3. **Type** every hole per §4, inserting adapters where allowed and
   reporting mismatches.
4. **Fold statics.** Every maximal run of bytes containing no hole,
   including whole nested objects and arrays, becomes one `Copy` of an
   interned byte range. This is the pass that gives static arms their
   O(1) render cost regardless of depth.
5. **Validate** against the encoding's structural rules.
6. **Lower.** Each hole expression compiles to an anonymous binding of
   the enclosing program, or the wire itself when the hole names one.
   The tile compiles to one `tile_render` node whose wire inputs are
   the hole values in skeleton order and whose constant is the
   skeleton, each `Hole` carrying its encoding, position, declared
   type, format, and raw flag. A projection body compiles to a child
   program keyed by its lexical position, exactly as a `for` body does,
   with one `IterationExtern` per element and one binding per hole in
   the body; the `Repeat` instruction carries its identity. One program
   per position, however many tuples flow.

Tiles declared inside a `for` body compile inside that body's program.
A tile declared inside a module body inlines with the call under the
module prefix ([Module System](module_system.md) §7).

A projection body compiles with the default compiler settings: it sees
registered nodes and the embedded library, not the enclosing program's
source directory, library paths, pragmas, or program-local modules
(`polydat-core/src/dsl/tile_lower.rs`, `polydat-core/src/library/tile_render.rs`). A `for` body
inherits all of those from its parent; the two lowerings differ here.

## 7. Runtime

### 7.1 The render program

A tile renders by walking its skeleton once, the same walk on every
engine. `Copy` appends an interned static run; `Hole` encodes the hole's
value, a borrowed view of the wire, straight into the output; `Branch`
reads its condition's truth and selects a body; `Repeat` takes its
tuples, binds each tuple into a kernel over the body program that the
rendering state keeps, and renders the body per tuple with the
separator between.

The hole values are the render node's inputs: the compiler binds each
hole expression as a binding of the enclosing program (or uses the wire
itself when the hole names one) and passes the values to one
`tile_render`, whose skeleton carries each hole's encoding. The renderer
encodes at the hole, from a borrowed view of the value, into whatever
sink the engine provides, so a hole costs one encode and one copy on
every engine and no spec is parsed at render time.

**Memoized tuples.** A projection whose comprehension names no generator
clause and no `{name}` placeholder in its sources has the same tuples
every render. They are evaluated once, when the `TileProgram` is
constructed, and reused. Any other projection evaluates its tuples at
render time with the evaluator the `for` runtime opens a traversal with,
after binding its generator clauses from the render node's inputs.

**Bodies.** Every projection body renders in a kernel of its own over
the body's program, kept by the rendering state in the render step's
own scratch ([Compiled By-Reference Slots](compiled_handles.md) §3), so
no body kernel belongs to a thread or is shared between states. The
body's program for `Engine::default()` is compiled when the
`TileProgram` is constructed, so the first render pays no compile. The
comprehension and the static runs are parsed and interned once, at that
construction. The body's inputs (the tuple elements and the cascade) and
its holes are resolved to indices on the first tuple and kept, so a
tuple is bound and read with no name lookup. One body kernel per body
program and engine is kept per rendering state and reused across its
renders; two renders never share dispense state.

**Number writers.** An integer hole with no format writes its digits
straight into the sink, and a float hole with no format or under a `.N`
precision writes through the crate's float writer, on every engine, with
the same bytes: the float writer is byte-identical to Rust's `Debug`
form and to `format!("{:.N}")` for every value, which is the contract a
tile's bytes impose (a shortest-representation writer that does not
promise that identity is not acceptable). Other formats keep the general
formatting path.

**Lifetime.** `TileProgram::interned` holds one program per skeleton
payload for the life of the process, so the same payload is parsed and
its bodies compiled once, and every render node over it shares the one
program. The program is built with no lock held, since a body with a
tile of its own interns through the same table.

### 7.2 Tiers

The tiers differ only in how the hole values arrive and where the
result goes, per [Compiled By-Reference Slots](compiled_handles.md):

- **P1.** `tile_render` walks the skeleton as an ordinary node on
  `Value`s, encoding each hole from a view of its value. The document
  is built in a `String` and surfaced as a `Str`, since a P1 value owns
  its bytes. A projection's body runs interpreted, in a kernel the
  rendering state keeps in the node's own scratch.
- **P2, and beside native segments in a hybrid kernel.** The render
  node runs its own closure over the slots
  (`compiled_slot = tile_render_compiled`): each hole is read as a
  borrowed view through its pair, a string or byte string as its
  bytes and a JSON or extension value as the `Value` the pair names,
  and the rendered document is written straight into the step's own
  string scratch, with no intermediate `String`; the step publishes
  the pair and a downstream adapter or `emit_row` reads it there. The
  body kernels of a projection live in the same step's scratch. One
  rule keeps semantics exact: the render node tolerates a `None` input
  (it writes `null`), so it takes its value through the `None` mask.
- **P3.** Native code calls the same closure in place
  ([Compiled By-Reference Slots]compiled_handles.md §6): the render
  node joins the segment or the cone its holes are computed in, its
  inputs gathered from their slots into the native frame, and it
  writes into the same entry of the state's scratch it would as a
  closure step, so the hole values never leave native code before
  they are encoded.
- **Projections on the compiled engines.** Every compiled kernel renders
  its bodies on `Engine::default()`, not on its own engine: the render
  node's closure serves the closure tier and a hybrid kernel's closure
  steps alike and has no engine to ask, and the default is the fastest
  engine the build has. The body's program for that engine is compiled
  when the tile program is constructed, so the first render pays no
  compile; the bodies render in the body kernels the rendering state
  keeps, so a body's own native code runs inside the render. A body
  the default engine refuses renders interpreted.

The measurements of the render path are in
[Native Tile Rendering](tile_native_rendering.md) §6.

### 7.3 Cost

Rendering a tile costs the bytes it copies, the holes it encodes, and,
for each projection, the tuple count times its body's cost. Skeleton
depth does not appear in that sum. A one-hole document with a
thousand-byte static arm renders in one copy and one encode.

Straight-line skeleton code per tile, a `memcpy` per static run and a
typed encoder call per hole, was not taken: with the allocations gone,
the walk costs ten nanoseconds an op, and the encoder is where the time
is.

## 8. Axioms

- **L1, Purity.** A tile's bytes are a pure function of its hole wires.
  Same coordinate, same bytes, on every engine and every host. Follows
  from D1 in the runtime model applied to the tile node.
- **L2, Static invariance.** The skeleton and every interned static range
  are fixed at compile time. No render re-serializes structure.
- **L3, Cost.** Render cost is linear in output bytes plus hole count plus
  projection tuples, independent of skeleton depth.
- **L4, Encoding soundness.** A `json` tile whose holes carry values of
  their compile-time types renders valid JSON. A `csv` tile renders a
  valid record. Guaranteed by compile-time validation plus per-type
  encoders.
- **L5, Form equivalence.** A textual `json` tile and the structural
  template it parses to compile to the same skeleton and render the same
  bytes.
- **L6, Type determinism.** Every hole's encoder is fixed at compile
  time from its declared, wire, or contextual type. No render inspects a
  value's runtime variant to choose an encoding.

## 9. Boundaries

Not in scope:

- binary encodings such as protobuf and Avro, which need length-prefix
  and back-patch instructions in the skeleton; the instruction set is
  designed to grow those without changing the grammar;
- parsing rendered output back into values;
- unbounded projections;
- recursion or user-defined template functions; splicing is the only
  composition, and it is static;
- YAML as an output encoding; YAML is supported as a carrier of
  structural templates, which is the same as JSON once loaded.

A tile is not a general templating language. Its expressions are
Polydat expressions, its loops are comprehensions, and its structure is
fixed.

## 10. Worked example

The toy test definition's load statement as a tile, with a JSON document
per reading written in the structural form as it would sit in a
workload file, and its textual twin. The textual form is an abridged
twin of the definition
([`examples/toy_test_definition.polydat`](../../examples/toy_test_definition.polydat);
the file adds `kind` and `flagged` holes and writes its string holes
bare, quoted by their wire type):

```json
{
  "meta": { "schema": 3, "source": "polydat", "units": { "temp": "C", "rh": "%" } },
  "tenant": "${tenant_id}",
  "device": "${device_id}",
  "ts": "${ts}",
  "reading": { "temp": "${temp_c | .2}", "rh": "${humidity | .1}", "status": "${status}" },
  "samples": [ "@for s in 0..4", { "n": "${s}", "temp": "${temp_c + s | .2}" } ]
}
```

```text
tile doc : json := {
    "meta": { "schema": 3, "source": "polydat", "units": { "temp": "C", "rh": "%" } },
    "tenant": ${tenant_id},
    "device": "${device_id}",
    "ts": ${ts},
    "reading": { "temp": ${temp_c | .2}, "rh": ${humidity | .1}, "status": "${status}" },
    "samples": [ @for s in 0..4 { { "n": ${s}, "temp": ${temp_c + s | .2} } } ]
}

tile load := <<<
INSERT INTO ${keyspace}.${table} (tenant_id, device_id, ts, doc)
VALUES (${tenant_id}, '${device_id}', ${ts}, '${doc!}')
>>>
```

In the structural form, `"${tenant_id}"` and `"${ts}"` are value holes
and render bare because their wires are `u64`; `"${device_id}"` and
`"${status}"` render quoted because their wires are `Str`; the `meta`
arm is one static range. `doc`'s skeleton is eleven instructions with a
`Repeat` for `samples`. `load` splices nothing at compile time because
`doc` is dynamic; the raw hole copies `doc`'s rendered bytes into
`load`'s own entry. Rendering both per coordinate is two memcpy sequences,
a handful of integer and float encodes, and a four-tuple loop.