Skip to content

Bars Specification 0.1.0 — Implementation-Defined Behavior

The inverse of the core: a consolidated, normative enumeration of what a conforming workalike may legitimately do differently from kBars, and the constraint that still binds each such choice.

Bars conformance comes in two tiers, both carried under one shared version number (see Core vs. optional extensions):

  • Core — the template system alone.
  • Full — core plus the standard library: a baseline roster of built-in transforms and @env properties that a full-tier implementation must support.

This page governs the boundary around those two tiers: what an implementation may vary regardless of tier, namely (1) whether it ships the standard library at all — that is, whether it targets core or full — and (2) the additions and extensions it may layer on top of the standard library. A port is free to vary anything listed here without losing conformance at its chosen tier; it is not free to vary anything documented as core elsewhere in this specification (grammar, pre-pass, data model, rendering's segments/blocks/expressions/scoped-variables/partials, errors, source locations, number rendering, iteration order, and string code-point semantics), nor — at the full tier — the standard library's own pinned behavior.

The standard-library catalog itself (which transforms and @env properties make up the baseline, and their exact semantics) is specified separately; this page describes only what surrounds it.

Built-in transform roster

The standard library defines a baseline roster of named transforms that a full-tier implementation must support. A core-tier implementation need not provide any transforms at all. Beyond that baseline, two kinds of variation stay implementation-defined at either tier:

  • Additional transforms. An implementation may register any number of transforms beyond the standard library, under names the standard library does not use. kBars itself registers 46 across the string, math, list, and date/time families (upcase, append, map, format_date_time, and so on); which of those the standard library adopts as its baseline is settled in the standard-library catalog, and any kBars transform outside that baseline is simply a kBars addition.
  • Extensions of a standard-library transform. An implementation may widen a standard-library transform — for example, accepting an optional argument the standard library does not define — provided the standard-library behavior is preserved exactly for the inputs the standard library specifies. If the standard library requires url_decode to take zero arguments, an implementation whose url_decode also accepts one optional argument still conforms, as long as its zero-argument behavior matches. This is a deliberately risky freedom: should a later standard-library version give that argument a meaning that differs from the implementation's, the two collide — and that is the implementer's tradeoff to make, not a conformance violation today.

What still binds: if an implementation provides a transform whose semantics correspond to one of the five string/sort operations pinned in Data Model (size, slice, truncate, split, sort), that transform must count and compare in Unicode code points, not UTF-16 code units or bytes — that rule is core regardless of what the transform is named or which tier claims it. A reference to an unregistered transform name must be a hard failure (KB-2001, UnknownTransform); calling a transform with the wrong argument count must be a hard failure (KB-2002, TransformArity) if the implementation's transform model distinguishes arity at all.

See conformance/transforms/*.json and Rendering for the kBars roster, and the Transforms reference for the full baseline catalog.

The @env namespace

The standard library also defines a baseline of @env properties (kBars registers now) that a full-tier implementation must expose through an @env namespace. A core-tier implementation need not have an @env namespace, any particular property, or any property-registration API at all. On top of the standard-library baseline, two things stay implementation-defined:

  • Additional properties. An implementation may register @env properties beyond the standard library's baseline.
  • How a clock-seeded property is sourced. What clock seeds a now-like property is the implementation's own choice; only such a property's observable value-kind and resolution behavior are pinned by the standard library, never a specific instant.

What still binds: whenever an implementation exposes an @env-equivalent namespace, an unresolved reference within it (unknown property name, or a sub-pointer applied to a non-container value) must render empty — not the literal "null" that an ordinary absent-context lookup falls back to (see Data Model). If an implementation supports the {{= }} scoped-variable tag at all, a sub-pointer assign target (@env/a/b) must be rejected with KB-3002 (AssignTargetSubPointer). These rules are core: they bind a core-tier implementation that chooses to offer @env just as much as they bind a full-tier one.

See conformance/env-properties/*.json and Rendering for the kBars roster, and the Environment Properties reference for the full baseline catalog.

Tunable limits

The default maximum partial-recursion depth is implementation-defined. kBars's default is its DEFAULT_MAX_PARTIAL_DEPTH constant, currently 64 — a value specific to kBars, not a number other ports are expected to match. kBars exposes a builder method to override it per environment; a port may choose any other default, or no built-in default at all (requiring the host application to always specify one), without losing conformance.

What still binds: whatever depth a port enforces, exceeding it must be a soft failureKB-5007 (RecursionLimit): the over-deep partial is abandoned, rendering continues, and no exception is thrown. A port that instead lets unbounded recursion overflow its call stack, or that throws a hard failure/crashes the process, does not conform, regardless of what depth value triggers the check.

No conformance fixture depends on any implementation's default depth. Every fixture that exercises the recursion limit sets an explicit maxPartialDepth in its fixture JSON (see partials-fixture.schema.json) rather than relying on whatever default the runner under test happens to have configured. conformance/partials/soft-failures.json's recursion-limit-soft-fails-and-stops fixture is one example. A runner may therefore use any default (or require an explicit one, with no built-in default at all) and still pass the suite unchanged.

The maximum block-nesting depth is also implementation-defined — but its failure mode is the opposite of the partial-recursion limit above. kBars enforces a DEFAULT_MAX_NESTING_DEPTH constant bounding how deeply {{#each}}/{{#if}}/{{#with}}/{{#unless}}/{{#inline}}/{{#> }} blocks may nest inside one another. Unlike the partial-recursion limit, exceeding it is a hard parse-time failure — KB-1003 (NestingTooDeep) — not a soft-fail event: the template is rejected before rendering begins, with no partial output. This is deliberate: the recursive-descent parser itself would otherwise overflow the native call stack on a pathologically deep template, a condition no soft-fail recovery can happen after the fact. A port may choose any depth for this ceiling (or none, if its own parser is not recursion-bound and cannot overflow), provided that exceeding whatever ceiling it does enforce is a clean, located hard failure rather than a crash — see Adversarial inputs.

No conformance fixture depends on any implementation's nesting-depth ceiling either. conformance/render/torture-deep-nesting.json exercises deep-but-legal nesting at a modest, portable depth chosen to render successfully under any reasonable implementation's ceiling — it does not probe where any particular implementation's limit lies, since that limit is not portable.

Other explicitly-open behavior

  • Human-readable exception message text is non-normative and may change between kBars releases; only the errorCode is stable. A port's own message text is entirely its own choice — see Errors.
  • Which digit-generation algorithm produces shortest-round-trip number output is open (kBars ports the digit-generation core of Ryu); any correct shortest-round-trip generator (Grisu3, Dragon4, a bignum library, or a host Number.prototype.toString() that happens to already be ECMA-262-conformant) is acceptable, provided the output text matches the fixtures — see Data Model.
  • Internal representation of the three value-model kinds (element / date-time / empty) is open; only their externally observable render/compare/iterate behavior is normative — see Data Model.