moon_tera

    A deterministic Tera-style template engine for MoonBit

    template
    jinja
    tera
    ssg
    web
    rendering
    Download zip
    Author
    Version
    0.3.0
    License
    MIT
    Last updated
    last month
    Downloads
    9

    #moon_tera

    moon_tera is a deterministic, dependency-light Tera-style template engine implemented in MoonBit. It is designed for embedded rendering, static-site generation, configuration files, and prompt templates where predictable output and a portable core matter more than full Jinja compatibility.

    The implementation is inspired by the syntax and behavior of Keats/tera, but is written for MoonBit's type system and package model. Rendering currently uses a direct AST interpreter.

    #Highlights

    • Expressions: literals, variables, dotted and indexed access, arithmetic, comparisons, and / or / not, filters, and is tests.
    • Control flow: if / elif / else, for with an empty branch, and set.
    • Multi-template rendering: extends, block, super(), and include.
    • Deterministic Value objects that preserve insertion order.
    • Explicit RenderError results for missing templates, missing parents, missing includes, inheritance cycles, and include cycles.
    • Whole-registry structural validation through Tera::validate().
    • Built-in string, collection, number, escaping, and JSON filters.
    • No third-party runtime dependencies.
    • 82 tests executed on wasm, wasm-gc, JavaScript, and native in CI.

    #Install

    After the package is available on MoonCakes:

    moon add btlqql/moon_tera@0.3.0

    Import the root package:

    import {
    "btlqql/moon_tera" @tera,
    }

    #Quick start

    let engine = @tera.Tera::new()
    engine.add_template("hello", "Hello {{ name | upper }}!")

    let context = @tera.object_value([
    ("name", @tera.str_value("MoonBit")),
    ])

    match engine.try_render("hello", context) {
    Ok(output) => println(output)
    Err(error) => println(error.message())
    }

    Validate every registered template before serving requests or generating a site:

    match engine.validate() {
    Ok(()) => println("template registry is valid")
    Err(error) => println(error.message())
    }

    Run the included end-to-end example:

    moon run cmd/main --target js

    Expected output:

    <main><h1>MOON TERA</h1><p>deterministic output</p><p>portable MoonBit core</p></main>

    #Verification

    moon fmt --check moon check --deny-warn moon build --target all --deny-warn moon test --target all --deny-warn moon run cmd/main --target js

    #Scope

    moon_tera intentionally implements a focused Tera-compatible subset. It does not currently support macros, imports, user-defined filters, streaming output, or automatic HTML escaping. Unsupported syntax must not be assumed to behave like upstream Tera. See COMPATIBILITY.md for the exact boundary and planned work.

    #Project structure

    PathResponsibility
    value.mbtOrdered dynamic values and deterministic JSON encoding
    lexer.mbt, token.mbtTemplate-level tokenization
    expr.mbtExpression lexer and Pratt parser
    parser.mbt, ast.mbtTemplate parser and AST
    vm.mbtAST evaluator
    filters.mbt, is_tests.mbtBuilt-in filters and tests
    tera.mbt, error.mbtTemplate registry, inheritance, validation, diagnostics
    cmd/mainRunnable integration example

    #Project materials

    #License and attribution

    The project is released under the MIT License. Tera attribution and the upstream license are preserved in NOTICE and LICENSE-TERA.

    Expr

    pub(all) enum Expr {
    EInt(Int)
    EFloat(Double)
    EStr(String)
    EBool(Bool)
    EVar(String)
    EMember(Expr, String)
    EIndex(Expr, Expr)
    EBinOp(String, Expr, Expr)
    ENot(Expr)
    EFilter(Expr, String, Array[Expr])
    EIs(Expr, String)
    ESuper
    } derive(
    Debug
    )

    A parsed template expression.

    ExprToken

    pub(all) enum ExprToken {
    EIdent(String)
    EInt(Int)
    EStr(String)
    EOp(String)
    EDot
    ELparen
    ERparen
    ELbracket
    ERbracket
    EComma
    EPipe
    EEof
    } derive(
    Debug
    )

    A token in the expression sub-language.

    Node

    pub(all) enum Node {
    Text(String)
    Output(Expr)
    If(Expr, Array[Node], Array[Node])
    For(String, Expr, Array[Node], Array[Node])
    Block(String, Array[Node])
    Include(String)
    Set(String, Expr)
    } derive(
    Debug
    )

    A node in a parsed template's syntax tree.

    RenderError

    pub(all) enum RenderError {
    TemplateNotFound(String)
    ParentNotFound(String)
    InheritanceCycle(String)
    IncludeNotFound(String)
    IncludeCycle(String)
    } derive(Eq,
    Debug
    )

    A structural error discovered before or during named-template rendering.

    RenderError::message

    fn RenderError::message(self : RenderError) -> String

    Return a stable diagnostic message suitable for CLIs and logs.

    Tera

    pub struct Tera {
    templates : Array[(String, Tpl)]
    }

    A template engine holding multiple named templates (for inheritance/include).

    Tera::add_template

    fn Tera::add_template(self : Tera, name : String, src : String) -> Unit

    Register a named template by parsing its source.

    Tera::clear

    fn Tera::clear(self : Tera) -> Unit

    Remove every registered template.

    Tera::contains_template

    fn Tera::contains_template(self : Tera, name : String) -> Bool

    Return whether a template is registered under name.

    Tera::new

    fn Tera::new() -> Tera

    Create an empty engine.

    Tera::remove_template

    fn Tera::remove_template(self : Tera, name : String) -> Bool

    Remove a registered template. Returns true when an entry was removed.

    Tera::render

    fn Tera::render(self : Tera, name : String, ctx : Value) -> String

    Render a registered template by name against a context.

    This compatibility API returns an empty string on structural errors. New applications should prefer try_render and surface its diagnostic.

    Tera::template_count

    fn Tera::template_count(self : Tera) -> Int

    Return the number of registered templates.

    Tera::template_names

    fn Tera::template_names(self : Tera) -> Array[String]

    Return registered template names in insertion order.

    Tera::try_render

    fn Tera::try_render(self : Tera, name : String, ctx : Value) -> Result[String, RenderError]

    Render a named template with structural validation and explicit errors.

    Tera::validate

    fn Tera::validate(self : Tera) -> Result[Unit, RenderError]

    Validate every registered template without rendering output.

    This preflight check catches missing parents, inheritance cycles, missing includes, and include cycles anywhere in the registry, including templates that have not been rendered yet.

    Token

    pub(all) enum Token {
    Text(String)
    Variable(String)
    Tag(String)
    } derive(Eq,
    Debug
    )

    A token produced by the template-level lexer.

    The lexer splits raw template text into three kinds of segments: literal text, variable expressions ({{ ... }}), and block tags ({% ... %}). Comments ({# ... #}) are discarded during lexing.

    Tpl

    type Tpl

    A parsed template: its nodes, the extends parent name, and block defs.

    Value

    pub(all) enum Value {
    Null
    Bool(Bool)
    Int(Int)
    Float(Double)
    Str(String)
    Array(Array[Value])
    Object(Array[(String, Value)])
    } derive(
    Debug
    )

    A dynamic value used as the template rendering context and intermediate data.

    Value::describe

    fn Value::describe(self : Value) -> String

    Return a stable, human-readable description for diagnostics and tests.

    Value::is_truthy

    fn Value::is_truthy(self : Value) -> Bool

    Truthiness used by {% if %} and filters like default.

    Following Tera/Jinja: Null, Bool(false), Int(0), Float(0.0), empty Str/Array/Object are falsy; everything else is truthy.

    Value::to_json_string

    fn Value::to_json_string(self : Value) -> String

    Encode a value as deterministic JSON while preserving object insertion order.

    apply_filter

    fn apply_filter(name : String, input : Value, args : Array[Value]) -> Value

    Apply a named filter to a value with already-evaluated arguments. Unknown filters pass the input through unchanged.

    apply_test

    fn apply_test(name : String, v : Value) -> Bool

    Apply a named is test to a value. Unknown tests return false.

    array_value

    fn array_value(a : Array[Value]) -> Value

    Construct an array value.

    bool_value

    fn bool_value(b : Bool) -> Value

    Construct a boolean value.

    expr_lex

    fn expr_lex(s : String) -> Array[ExprToken]

    Lex an expression string into tokens.

    expr_parse

    fn expr_parse(toks : Array[ExprToken]) -> Expr

    Parse a token list into an Expr.

    float_value

    fn float_value(f : Double) -> Value

    Construct a float value.

    int_value

    fn int_value(i : Int) -> Value

    Construct an integer value.

    lex

    fn lex(src : String) -> Array[Token]

    Lex a template string into top-level tokens.

    lookup

    fn lookup(field : String, ctx : Value) -> Value

    Look up a field in a Value context. Returns Null when the field is absent or the context is not an object.

    null_value

    fn null_value() -> Value

    Construct a null value.

    object_value

    fn object_value(o : Array[(String, Value)]) -> Value

    Construct an object value from insertion-ordered key/value pairs.

    parse

    fn parse(tokens : Array[Token]) -> (Array[Node], String)

    Parse tokens into AST nodes and the extends parent name ("" if none).

    parse_expr

    fn parse_expr(s : String) -> Expr

    Parse a raw expression string into an Expr (lexer + Pratt parser).

    render

    fn render(template : String, ctx : Value) -> String

    Render a standalone template string against a context Value.

    render_nodes

    fn render_nodes(nodes : Array[Node], ctx : Value) -> String

    Render a parsed template (list of nodes) against a context (no inheritance).

    str_value

    fn str_value(s : String) -> Value

    Construct a string value.