WEP: Markup Dialect — Where the Top Level Lives
Context
Wado has to emit HTML on two fronts and has a surface for neither.
- Server-side. The blog at
wado-lang.github.iorenders through Marl, and awasi:http/servicehandler dispatched bycore:routerreturns HTML. Marl already exportsescape_text/escape_attrfor HTML-templating consumers; the consumer does not exist. - Client-side.
wado-lang:webis eight resources generated from a WebIDL slice, with no host to run against, and Reactive Signals is designed around a UI surface it does not have.
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
- The
<reservation stays reserved and unspent, at every position on the axis. - A dialect ships as a Kiln generator in its own repository, following the
package shape
package-gale-highlight-wadoalready demonstrates: a.g4beside the source, consumed at ausesite withgenerator: { module: "lib:gale" }. - Promotion of any surface into the language is out of this WEP. It becomes arguable only against evidence this study produces.
What the study must produce
- For Wado-hosted: a dialect grammar —
Wado.g4plus 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. - 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.
- 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.
- For every position: a diff of the generated output against the same component written by hand at Builder. Kiln makes the comparison mechanical.
- 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
- A source map for Kiln: a generator-supplied correspondence from output spans back to input spans, applied when the compiler renders a diagnostic. Recorded as a known gap in the Kiln WEP.
- Grammar import resolution in Gale.
import S;parses, but slave grammars are not resolved, so a dialect vendorsWado.g4and something has to keep the copy honest:mise run check-grammarholds the original to the compiler's parser, and nothing holds a copy to the original. Gale's TODO records the work as actionable on its own, and notes that the Kiln multi-input plumbing it needs already exists. - What the markup lowers to. Builder is the target every position needs, and it does not exist. No surface can be studied before it.
- Whether one surface serves both consumers, or the two shapes need two.
Consequences
- The JSX question is answered by construction rather than by argument: whatever
the study concludes ships outside the language, so the
<reservation is still there — bought with<Type as Trait>::method— for a surface that earns promotion. - A dialect exercises Kiln, Gale and the language at once — the first test of
whether Kiln's ceiling is "IDL converter" or "the mechanism a dialect needs" —
and gives Gale a second real-world grammar beyond
Wado.g4and its corpus. - A negative result is a result. If the diff against Builder shows the builder API reads well enough, no surface is built, and nothing was spent finding out.
Related WEPs
- Kiln — Keyed IDL Lowering Notation — the mechanism a dialect ships as
- Reactive Signals — reserves
<for JSX and assumes it in its codegen - Overload Resolution — spends the reservation, ruling out
<Type as Trait>::method - Gale — Grammar Adaptive LL Engine — the parser generator a dialect grammar feeds
- The Web Interface for Wado —
wado-lang:web, the client-side target - Marl — Markdown Renderer and Formatter — the server-side consumer, and its escaping helpers
- HTTP Path Router (
core:router) — the other server-side consumer - Tagged Template Literals — the interpolation-based surface; an
htmltag with contextual escaping is its worked example
