Wado

WEP: Markup Dialect — Where the Top Level Lives

Context

Wado has to emit HTML on two fronts and has a surface for neither.

The obvious import is JSX, and the ground is already prepared for it: a leading < in expression position belongs to JSX by Overload Resolution, which is why <Type as Trait>::method is permanently out and the trait-qualified form is what Wado spells instead. Reactive Signals writes its examples in JSX and lists "integrate with JSX codegen" as work.

None of it exists. There is no Element type, no component model, no event wiring; reactive is a reserved word the parser rejects. JSX would be the surface of a UI framework that has not been built.

What changed is that the surface question is now separable from the language question. Kiln admits any file as a generator input, including a Wado dialect, and a dialect adds nothing to the language: no lexer mode in wado, no parser rule, nothing for wado format, the LSP, or the highlighter to learn. A markup surface can therefore be built, published, used and abandoned outside this repository.

So the open question is not "JSX or not". It is which shape a markup surface should take, and the first axis to settle is where the top level lives.

Prior art

Four positions, distinguished by the nesting direction and by how many grammars own one file.

Position Shape Examples
Wado-hosted Wado at the top level, markup in expression position JSX, XHP/Hack, ReScript, Scala 2 XML literals, E4X
Markup-hosted Markup at the top level, Wado in holes PHP, ERB, Razor, Elixir HEEx, Jinja, Angular templates
Sectioned One file, sections, neither nested in the other Vue SFC, Svelte, Astro
Builder No markup surface; a typed builder API Flutter, SwiftUI, lucid/blaze, kotlinx.html

Languages with a macro system never touched their grammar — Rust (html!, view!, rsx!), Elixir (~H), OCaml (PPX), Haskell (QuasiQuotes) all reached Wado-hosted or Markup-hosted through a macro, not through syntax. And of the languages that did put markup in the grammar, two removed it: E4X was deleted, and Scala 3 deprecated XML literals in favour of interpolators. Wado has no macros, which is why the question reaches the grammar at all — and Kiln is the answer that keeps it away from the grammar.

The record also refuses to converge on one position. Server-side rendering settled on Markup-hosted (Razor, HEEx, templ); client-side UI settled on Wado-hosted and Sectioned (JSX, Svelte). Wado has one consumer of each shape.

Decision

This WEP fixes the axis, the names for its positions, and the criteria. It proposes no change to the Wado language.

The axis

The four positions above are the axis. Builder is the control: without it the study can only compare markup surfaces to each other, never establish that one is needed.

Criteria

Position Grammar reuse Diagnostics today HTML-authorable Control flow Consumer it fits
Wado-hosted Wado.g4 + a markup mode layout-preserving expansion no the language's own playground, signals
Markup-hosted needs an HTML parser needs a source map yes a template language's blog, wasi:http/service
Sectioned needs an HTML parser needs a source map yes a template language's either
Builder none exact no the language's own either

HTML-authorable is one property under two names: whether real HTML pastes in unedited, and whether someone who does not write Wado can own the file.

Findings

Facts about this repository bear on the criteria; none of them settles the axis.

Lexer modes are proven on the real grammar. Wado.g4 already lexes a nested language: a backtick pushes mode TEMPLATE, ${ pushes DEFAULT_MODE back for the interpolated expression, and a brace pair balances the return. Wado-hosted markup needs that machinery with < in place of the backtick. Gale accepts mode inside a combined grammar — a deliberate superset over ANTLR4, which restricts modes to a lexer grammar — so a dialect grammar keeps the shape Wado.g4 already has.

No HTML parser exists here. Marl escapes HTML and never parses it, by design. Markup-hosted and Sectioned both need one, and tolerant HTML5 parsing — implied end tags, raw-text elements, attribute quirks — is a larger artefact than the experiment it serves. An XML-strict subset avoids it, and discards the one advantage those positions hold over Wado-hosted.

Diagnostics have two answers, and only one of them exists today. The compiler reports its own type errors against the generated source; only a generator's emit-diagnostic spans reach the input file. Layout preservation — emitting a module whose lines correspond to the input's — is a generator-side discipline needing no compiler change, but it is available only where the output is an in-place expansion of the input, which is to say Wado-hosted. A source map is the general answer: the generator already knows the correspondence, so Kiln carrying it from output spans back to input spans serves every position equally. templ, Svelte and Vue all ship one. It is Kiln infrastructure rather than language surface, so it costs a dialect none of its independence.

A Markup-hosted file cannot bind to a companion type by name. Angular and Razor Pages do exactly that — the template names a companion class's members and the compiler resolves them — but a Kiln generator receives files by value with no access to the compiler's types, so it could reproduce that only by parsing the sibling .wado itself and redoing name resolution the compiler already does. A Markup-hosted file therefore declares its own typed parameters, and the exported render function is its whole interface.

Deliberate omissions

What the study must produce

  1. For Wado-hosted: a dialect grammar — Wado.g4 plus a markup mode — parsing a realistic component with Gale's resilient CST intact. The known hazard is >: closing a tag and comparing two values want the same character, in a grammar with no JSX-style expression/type disambiguation to lean on.
  2. For Wado-hosted: a layout-preserving generator, and the measured cost of that discipline — which markup forms cannot expand within their own line count. A source map retires this question.
  3. For Markup-hosted and Sectioned: which HTML subset the parser accepts, and the paste-fidelity bill of that choice, measured by converting the blog's real HTML rather than estimated. For Markup-hosted, also how a file takes its data — typed parameters alone, or Wado items of its own alongside them.
  4. For every position: a diff of the generated output against the same component written by hand at Builder. Kiln makes the comparison mechanical.
  5. A compile-time budget. Gale parses a 13366-byte fixture in ~4.4 ms/iter (build only, dev host, guest -O2); a dialect file re-parses on every edit because its content is part of the cache key.

Known gaps

Consequences