Skip to content

Rea Language Specification — Part 4: Utilities (Sections 22–27)

Back to main specification

Implementation status: Sections 22–27 describe advanced features (pluralization & localization, content protection, captions, escaping, comments, error handling) that are specified but not yet implemented in the proof-of-concept parser. The error handling philosophy (graceful degradation) is followed in the parser design. See REA-CHEATSHEET.md for detailed status.


22. Pluralization & Localization

Rea provides built-in functions for grammatically correct text across all languages. These replace any need for custom condition syntax by leveraging CLDR plural rules and standard internationalization APIs.

Pluralization with plural()

The plural() function maps a count to the correct grammatical form using CLDR plural categories. Categories vary by language — English has 2 (one, other), Slovak has 4 (one, few, many, other), Arabic has 6.

rea
{plural(gold, zero="no coins", one="{} coin", other="{} coins")}

For 0: "no coins", for 1: "1 coin", for 5: "5 coins". The {} placeholder inserts the count value.

Slovak (4 categories):

rea
{plural(count, one="{} pero", few="{} perá", other="{} pier")}

For 1: "1 pero", for 3: "3 perá", for 5: "5 pier".

CLDR plural categories:

CategoryEnglish exampleUsed by
zero0 itemsArabic, Latvian, Welsh
one1 itemMost languages
two2 itemsArabic, Hebrew, Slovenian
few2-4 itemsCzech, Slovak, Polish, Russian
many5+ itemsPolish, Russian, Arabic
otherdefaultAll languages (required fallback)

The runtime resolves categories based on the story's language metadata. Authors only provide the categories their language requires — other is the mandatory fallback.

Text selection with select()

The select() function maps a string value to text variants. Use it for gender, pronoun, role-based, or any key-based text variation:

rea
{select(pronoun, he="He draws his sword", she="She draws her sword", other="They draw their sword")}

other is the fallback for unmatched values.

Role-based variation:

rea
{select(reader.class, warrior="You swing your blade", mage="You cast a spell", other="You act")}

Number formatting with format()

The format() function delegates to locale-aware number formatting (equivalent to Intl.NumberFormat):

rea
Score: {format(player.score, style="decimal", grouping=true)}
ParameterValuesDefault
styledecimal, percent, currencydecimal
groupingtrue, falsefalse
min_fractioninteger (minimum decimal digits)0
max_fractioninteger (maximum decimal digits)3
currencyISO 4217 code (e.g. EUR, USD)story locale
rea
Price: {format(item.price, style="currency", currency="EUR")}
Chance: {format(hit_rate, style="percent")}
Distance: {format(meters, min_fraction=1, max_fraction=1)} m

Fantasy calendars with calendar()

The calendar() function maps real date components to custom names — perfect for fantasy world-building:

rea
The month of {calendar(world.date, month="Frost,Bloom,Fire,Rain,Wind,Sun,Storm,Harvest,Mist,Shadow,Ice,Star")}

For January: "Frost", for March: "Fire", for December: "Star".

ParameterDescription
monthComma-separated list of 12 month names
weekdayComma-separated list of 7 day names (Monday = first)
eraExpression defining era calculation
rea
Day of {calendar(world.date, weekday="Moonday,Fireday,Waterday,Earthday,Windday,Lightday,Darkday")},
{calendar(world.date, month="Frost,Bloom,Fire,Rain,Wind,Sun,Storm,Harvest,Mist,Shadow,Ice,Star")} the
{ordinal(world.date.day)}.

Ordinal numbers with ordinal()

rea
You finished in {ordinal(position)} place.

Returns locale-appropriate ordinals: 1st, 2nd, 3rd in English; 1., 2., 3. in Slovak/German.


23. Content Protection (Lock)

The {lock} command protects story content, preventing readers from accessing chapters until conditions are met. This supports the platform's progressive download and monetization model.

Soft lock

Content is bundled but hidden until the reader solves a puzzle or meets a condition. The key is derived from the correct answer using PBKDF2 + AES-GCM:

rea
{lock type="soft", key="a1b2c3d4e5f6g7h8i9j0" begin}
  This chapter only unlocks when the reader provides the correct answer.
{end lock}

Multiple valid answers:

rea
{lock type="soft", key=["hash_answer_1", "hash_answer_2"] begin}
  Either answer unlocks this content.
{end lock}

How soft lock works internally:

  1. The author provides a plain-text answer during story creation
  2. The platform derives an AES-256-GCM key using PBKDF2 (SHA-256, 100k iterations) from the answer + random salt
  3. The locked content is encrypted with the derived key
  4. The salt and IV (12 bytes) are stored alongside the ciphertext
  5. When the reader submits an answer, the platform re-derives the key and attempts decryption
  6. AES-GCM's built-in authentication tag verifies the answer is correct (tamper-proof)

Hard lock

Content is stored on the server and downloaded only after the reader submits the correct key. This prevents extraction from the local package:

rea
{lock type="hard", key="server_stored_hash" begin}
  This chapter is downloaded only after correct verification.
{end lock}

Hard locks use server-side validation: the reader's answer is hashed client-side and sent to the server, which compares it against the stored hash and returns the encrypted content only on match.

Conditional lock

Lock content behind story conditions:

rea
{lock condition="player.level >= 10 and has_dragon_scale" begin}
  The ancient text reveals itself only to the worthy.
{end lock}

Encryption model

All content encryption in Rea uses the Web Crypto API for browser-safe, standards-compliant cryptography:

ComponentAlgorithm / Standard
EncryptionAES-256-GCM (authenticated encryption)
Key derivationPBKDF2 (SHA-256, 100k+ iterations)
IV12-byte random (per-block, never reused)
Auth tag128-bit (built into AES-GCM)
Key exchangeX25519 (cooperative readers, server-client)
HashingSHA-256 (checksums, answer verification)
SigningEd25519 (package signatures, author ID)

The encryption model ensures:

  • No plaintext in packages — locked content is always ciphertext in the .reast file
  • Forward secrecy — each lock block uses a unique IV; compromising one doesn't expose others
  • Browser compatibility — all algorithms work in Chrome, Firefox, Safari, and Edge via SubtleCrypto
  • Offline-capable — soft locks decrypt locally without server contact

24. Captions

The {caption} command adds descriptive captions to preceding content (images, code blocks, or text sections):

rea
[!The ancient map < media/map.jpg]
{caption "A hand-drawn map found in the wizard's tower"}

{voice speaker="elena", emotion="sad" begin}
  I never thought it would end this way.
{end voice}
{caption "Elena's final words"}

25. Escaping & Raw Text

Escaping special characters

Use \ to escape any character with special meaning:

rea
The price is \{not a command\}.
Use \_underscores\_ without italics.
The path was \*not\* what it seemed.

Raw blocks

Content inside {raw begin} is rendered as-is with no processing:

rea
{raw begin}
  This {text} is *not* processed.
  No _formatting_ or {commands} apply here.
{end raw}

26. Comments

Author comments (hidden from reader)

rea
{// This is a single-line comment}

{comment begin}
  This is a multi-line comment.
  Readers never see this.
{end comment}

Single-line comments use {// text}. The // token causes the parser to ignore everything inside the braces — including any keywords like begin. For example, {// this has begin at the end begin} is still a valid single-line comment.

Multi-line comments use the {comment begin}...{end comment} block syntax, consistent with all other paired commands.

TODO markers (compile-time warnings)

rea
{todo: Write the battle scene here}

The platform/compiler shows these as warnings during development.

Notes (development annotations)

rea
{note: This section needs playtesting with 3+ readers}

27. Error Handling

Error categories

Rea distinguishes three categories of errors:

CategoryWhen detectedExamples
Parse errorBefore executionSyntax errors, unclosed blocks, malformed metadata, invalid nesting
Runtime errorDuring executionUndefined variable, division by zero, missing divert target, recursion limit
Environment errorDuring executionMissing media file, failed {include}, sensor unavailable, network failure

Parse errors are always reported before the story runs (in strict mode) or silently patched (in graceful mode). Runtime and environment errors occur during story execution and are subject to graceful degradation.

Rea does not have try/catch. All error handling is implicit — the runtime silently recovers, and the reader's experience is never interrupted. Authors see errors during testing (strict mode); readers never do.

Graceful mode (default)

By default, Rea fails gracefully — the reader's experience is never broken:

ErrorGraceful behavior
Undefined variable {gold}Renders as empty string (nothing shown)
Invalid expression {1 / 0}Skipped silently
Missing imageShows placeholder with alt text
Missing audioSilently skipped, story continues
Missing videoPoster frame shown if available, else placeholder
Missing voice/TTSSilently skipped
Unclosed block {if begin} at EOFAuto-closed at end of file
Unknown command {magic}Treated as print expression
Failed {include "file.rea"}Include silently ignored (as if file was empty)
Failed plugin commandPlugin output treated as empty
Sensor unavailableworld.has("sensor") returns false; see Section 21

Strict mode

Enable strict mode for development and testing:

rea
{strict on}

In strict mode, errors are shown as inline warnings visible only to the author (in a preview/development view). Readers never see error messages — they always get graceful behavior.

Example strict mode warnings:

text
⚠ Line 42: Undefined variable "goldd" — did you mean "gold"?
⚠ Line 87: Unclosed block command {if begin} opened at line 83
⚠ Line 15: Missing media "media/castle.jpg" — file not found in package
⚠ Line 63: Expression error in {1 / 0} — division by zero
⚠ Line 10: Include "lib/missing.rea" — file not found in package
⚠ Line 25: Plugin "compass" failed — function "bearing" is not defined

Strict mode features:

  • Typo detection with suggestions (Levenshtein distance on variable/command names)
  • Line numbers in all warnings
  • Highlights unclosed blocks with the opening line reference
  • Validates media references against the package manifest
  • Validates {include} paths against the package structure

Fallback values

Where it makes sense, syntax supports optional inline fallback values:

rea
[!map < media/map.png fallback="media/map-lowres.png"]
[?thunder < sounds/thunder.mp3 fallback="sounds/rain.mp3"]

If the primary resource fails, the fallback is used. If the fallback also fails, the platform applies its default graceful behavior (placeholder for images, silence for audio, etc.).

External API access

External API calls (network requests from within a story) must be declared in the reast.json manifest via allowed_urls. URLs must not appear anywhere in .rea text — authors reference APIs by alias only. This ensures all external access is declared, auditable, and permission-controlled.

json
{
  "title": "Weather Story",
  "allowed_urls": [
    {
      "alias": "weather",
      "url": "https://api.weather.example.com",
      "params": ["lat", "lng"]
    },
    { "alias": "maps", "url": "https://maps.example.com" }
  ]
}

Each entry in allowed_urls is an object with:

FieldTypeDescription
aliasstringShort name used to reference this API in .rea
urlstringBase URL prefix the story may access
paramsstring[]Optional list of allowed query parameter names

Authors reference allowed APIs by alias in story code. If a request fails, the runtime returns undefined and the story continues.