Noizu.Tessera.Component.Template (Noizu.Tessera v0.2.0)

Copy Markdown View Source

SPIKE (spike/elixir-to-lit): ~TSR element-template parsing and lit-html emission (docs/notes/spike-elixir-to-lit.md §template-parser).

Two lexers produce the SAME token stream, and one tree-ifier builds the tree (parse/1):

  • Hologram path — when Hologram 0.10.1 is compiled into the path, the markup is tokenized by Hologram.Template.Parser.parse_markup/1 (a public, documented, pure function on an @moduledoc false module — de-facto internal-but-callable; see the notes for the stability discussion). Its flat parsed-tag stream is converted 1:1 into the subset token stream below.
  • Minimal path — a hand-rolled lexer for exactly the subset (plain elements, static/interpolated attributes, {expr}, {%if}/{%else}/{/if}, {%for pat <- expr}/{/for}, $event={:action[, params]}, {%region :name}/{%endregion}, {%super%}), used when Hologram is absent so spec-only consumers (npm/UMD packaging without a Hologram host) still compile.

Region-bearing markup ALWAYS lexes through the minimal lexer on both hosts (brief 2d, T1-1): Hologram's parse_markup does not know {%region%}, so lex/1 forces the minimal path whenever the markup mentions a region or the {%super%} marker — one token stream, one tree shape, on every host.

Token stream (the shared subset):

{:start_tag, tag, attrs} | {:self_closing_tag, tag, attrs} | {:end_tag, tag}
{:text, s} | {:expr, ast}
{:if_start, ast} | :else | :if_end
{:for_start, var, coll_ast} | :for_end
{:region_start, name} | :region_end | :super

attrs :: [{name, {:static, s} | {:parts, [{:text, s} | {:expr, expr_string}]}}
          | {:"$event", dom_event, binding_src}
          | {:"$bool", name, {:static, s} | {:parts, [...]}}]

?attr={expr} (Lit boolean attribute binding) shares the {:"$bool", …} form across both lexers; <slot>…</slot> (non-void, fallback content) is supported under both lexers — the Hologram path renames slot tags through a reserved placeholder because Hologram's parser treats <slot> as void.

Tree shape:

{:element, tag, attrs, children} | {:text, s} | {:expr, ast}
{:if, ast, children, else_children} | {:for, var, coll_ast, children}
{:fragment, children}

to_lit_render/2 emits a Lit render() body as nested html template literals.

Summary

Functions

Compile the default region bodies of a template tree into method fragments %{name: atom, js: "html..."} (the derived render() references them via ${this.__region_<name>()}). Duplicate region names raise a CompileError.

Compile one slot_template :name override body. {%super%} inside the body lowers to super.__region_<name>() (the base default fragment at that point in the chain).

Which lexer parse/1 uses right now.

Which lexer parse/1 will use for THIS markup (brief 2d / T1-1): region markup always lexes :minimal — Hologram's parser does not know {%region :name}/{%endregion}/{%super%} — everything else follows the host (:hologram when the dep is present).

Minimal lexer for exactly the subset (used when Hologram is not loaded).

Parse markup into the subset tree. Uses Hologram's template parser when compiled in, otherwise the minimal lexer. Returns {:ok, tree} or {:error, message} (message names the construct).

Declared regions of a parsed tree, preorder (outer before inner), as %{name: atom, nested: [atom]} where nested lists every region name appearing anywhere inside the region's body.

Emit the JavaScript expression for a Lit render() body (without the leading return). ctx is a Noizu.Tessera.Component.Expr context.

Tree-ify the shared token stream. Frames: {:elem, {tag, attrs, children}}, {:if, {ast, then, else}}, {:else, {ast, then, else}}, {:for, {var, coll, children}}. Top-level nodes accumulate in top.

Functions

compile_region_methods(tree, ctx)

@spec compile_region_methods(term(), Noizu.Tessera.Component.Expr.t()) :: [
  %{name: atom(), js: String.t()}
]

Compile the default region bodies of a template tree into method fragments %{name: atom, js: "html..."} (the derived render() references them via ${this.__region_<name>()}). Duplicate region names raise a CompileError.

compile_region_override(name, tree, ctx)

@spec compile_region_override(atom(), term(), Noizu.Tessera.Component.Expr.t()) :: %{
  name: atom(),
  js: String.t()
}

Compile one slot_template :name override body. {%super%} inside the body lowers to super.__region_<name>() (the base default fragment at that point in the chain).

lexer()

@spec lexer() :: :hologram | :minimal

Which lexer parse/1 uses right now.

lexer_for(markup)

@spec lexer_for(String.t()) :: :hologram | :minimal

Which lexer parse/1 will use for THIS markup (brief 2d / T1-1): region markup always lexes :minimal — Hologram's parser does not know {%region :name}/{%endregion}/{%super%} — everything else follows the host (:hologram when the dep is present).

minimal_lex(markup)

@spec minimal_lex(String.t()) :: {:ok, list()} | {:error, String.t()}

Minimal lexer for exactly the subset (used when Hologram is not loaded).

parse(markup)

@spec parse(String.t()) :: {:ok, term()} | {:error, String.t()}

Parse markup into the subset tree. Uses Hologram's template parser when compiled in, otherwise the minimal lexer. Returns {:ok, tree} or {:error, message} (message names the construct).

regions(tree)

@spec regions(term()) :: [%{name: atom(), nested: [atom()]}]

Declared regions of a parsed tree, preorder (outer before inner), as %{name: atom, nested: [atom]} where nested lists every region name appearing anywhere inside the region's body.

to_lit_render(tree, ctx)

@spec to_lit_render(term(), Noizu.Tessera.Component.Expr.t()) :: String.t()

Emit the JavaScript expression for a Lit render() body (without the leading return). ctx is a Noizu.Tessera.Component.Expr context.

treeify(arg1, stack, top)

@spec treeify(list(), list(), list()) :: {:ok, term()} | {:error, String.t()}

Tree-ify the shared token stream. Frames: {:elem, {tag, attrs, children}}, {:if, {ast, then, else}}, {:else, {ast, then, else}}, {:for, {var, coll, children}}. Top-level nodes accumulate in top.