Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Romit is a declarative language for describing state, events, rules, and tests. Programs use the .rmt file extension and the romit command-line tool.

Romit’s surface syntax is brace-free: no { }, [ ], or ( ). Blocks are shown only by indentation (two spaces per level; tabs are rejected with P001).

on Run
  rule say_it
    when true
    emit Greet with message "Hello, world!"

The compiler in this repository implements Stage 0–1: specs, checks, tests, and a small interpreter for beginner examples. UI app / screen syntax is documented under planned examples.

Install Romit

Pre-built binaries ship on GitHub Releases for Linux x86_64-unknown-linux-gnu and aarch64-unknown-linux-gnu, macOS x86_64-apple-darwin and aarch64-apple-darwin, and Windows x86_64-pc-windows-msvc. Linux builds use glibc dynamic linking on the official GNU triples.

One-line install

macOS and Linux

curl -fsSL https://romitlang.org/install.sh | sh

Windows

irm https://romitlang.org/install.ps1 | iex

Set ROMIT_MODIFY_PATH=1 before piping if you want the installer to append ~/.romit/bin to your shell config or Windows user PATH automatically.

macOS

Download the .tar.gz for your CPU:

HardwareAsset triple
Apple Siliconaarch64-apple-darwin
Intel Macx86_64-apple-darwin
export ROMIT_VERSION=0.1.0
curl -fsSL -O "https://github.com/romit07/romitlang/releases/download/v${ROMIT_VERSION}/romit-${ROMIT_VERSION}-aarch64-apple-darwin.tar.gz"
curl -fsSL -O "https://github.com/romit07/romitlang/releases/download/v${ROMIT_VERSION}/SHA256SUMS"
sha256sum -c SHA256SUMS --ignore-missing
tar -xzf "romit-${ROMIT_VERSION}-aarch64-apple-darwin.tar.gz"
cp romit-*/romit ~/.romit/bin/romit
chmod +x ~/.romit/bin/romit

PATH — bash / zsh — add to ~/.zshrc or ~/.bash_profile:

export PATH="$HOME/.romit/bin:$PATH"

PATH — fish — in ~/.config/fish/config.fish:

fish_add_path $HOME/.romit/bin

Linux

ArchitectureAsset triple
amd64x86_64-unknown-linux-gnu
arm64aarch64-unknown-linux-gnu
export ROMIT_VERSION=0.1.0
curl -fsSL -O "https://github.com/romit07/romitlang/releases/download/v${ROMIT_VERSION}/romit-${ROMIT_VERSION}-x86_64-unknown-linux-gnu.tar.gz"
curl -fsSL -O "https://github.com/romit07/romitlang/releases/download/v${ROMIT_VERSION}/SHA256SUMS"
sha256sum -c SHA256SUMS --ignore-missing
mkdir -p ~/.romit/bin
tar -xzf "romit-${ROMIT_VERSION}-x86_64-unknown-linux-gnu.tar.gz" -C /tmp
cp /tmp/romit-*/romit ~/.romit/bin/
chmod +x ~/.romit/bin/romit

Use the same PATH snippets as macOS.

Windows

Download romit-${ROMIT_VERSION}-x86_64-pc-windows-msvc.zip from Releases, verify against SHA256SUMS, and extract romit.exe to %USERPROFILE%\.romit\bin.

Add that folder to the User PATH in Settings → System → Environment variables, or:

$env:ROMIT_MODIFY_PATH = '1'
irm https://romitlang.org/install.ps1 | iex

Verify checksums

Every release includes SHA256SUMS. After downloading an archive and the sums file:

sha256sum -c SHA256SUMS --ignore-missing

On Windows PowerShell:

Get-FileHash .\romit-0.1.0-x86_64-pc-windows-msvc.zip -Algorithm SHA256
# Compare with the matching line in SHA256SUMS

Update

Re-run the one-line installer, or download the newer release asset and replace the binary in ~/.romit/bin.

Pin a version:

ROMIT_VERSION=0.1.0 curl -fsSL https://romitlang.org/install.sh | sh

Uninstall

curl -fsSL https://romitlang.org/install.sh | sh -s -- --uninstall

Remove any PATH lines you added manually. On Windows:

irm https://romitlang.org/install.ps1 | iex; install.ps1 -Uninstall

Or delete %USERPROFILE%\.romit\bin\romit.exe and remove the directory from user PATH.

Build from source

Requires a Rust toolchain:

git clone https://github.com/romit07/romitlang.git
cd romitlang
cargo build --release
./target/release/romit --version

Copy target/release/romit anywhere on your PATH.

Troubleshooting

ProblemWhat to try
curl: 404 on installNo GitHub Release yet, or set ROMIT_VERSION to a published tag.
Checksum mismatchRe-download both the archive and SHA256SUMS; check for partial downloads.
romit: command not foundEnsure ~/.romit/bin is on PATH and start a new shell.
Wrong Linux archPick the GNU triple that matches uname -m.
Corporate proxySet HTTPS_PROXY for curl / Invoke-WebRequest.

For local installer development, point at a directory served over HTTP:

ROMIT_BASE_URL=http://127.0.0.1:8765 ROMIT_VERSION=0.1.0-test sh install.sh

Getting started on Linux

After installing Romit:

romit --version

Create hello.rmt:

spec HelloWorld version 1

output Greet
  message text

event Run

on Run
  rule say_it
    when true
    emit Greet with message "Hello, world!"

test says_hello
  send Run
  expect emitted Greet with message "Hello, world!"

Check and test:

romit check hello.rmt
romit test hello.rmt

Try the bundled beginner examples:

git clone https://github.com/romit07/romitlang.git
cd romitlang
romit test examples/beginner/hello_world.rmt

Official Linux releases target glibc GNU triples. Very old distributions may need to build from source with a current Rust toolchain.

Getting started on macOS

After installing Romit:

romit --version

Create hello.rmt in a new folder:

spec HelloWorld version 1

output Greet
  message text

event Run

on Run
  rule say_it
    when true
    emit Greet with message "Hello, world!"

test says_hello
  send Run
  expect emitted Greet with message "Hello, world!"

Type-check it:

romit check hello.rmt

Run tests in the file:

romit test hello.rmt

The repository example is the same program:

romit test examples/beginner/hello_world.rmt

Use romit check --no-coverage <file> on large specs when you want to skip rule-coverage diagnostics.

Getting started on Windows

After installing Romit, open a new terminal so PATH updates apply:

romit --version

Create hello.rmt with your editor:

spec HelloWorld version 1

output Greet
  message text

event Run

on Run
  rule say_it
    when true
    emit Greet with message "Hello, world!"

test says_hello
  send Run
  expect emitted Greet with message "Hello, world!"

From PowerShell in that folder:

romit check hello.rmt
romit test hello.rmt

Clone the repo to run the same file from examples:

git clone https://github.com/romit07/romitlang.git
cd romitlang
romit test examples\beginner\hello_world.rmt

The CLI commands today are romit check and romit test — there is no separate romit run subcommand yet.

Hello world

spec HelloWorld version 1

output Greet
  message text

event Run

on Run
  rule say_it
    when true
    emit Greet with message "Hello, world!"

test says_hello
  send Run
  expect emitted Greet with message "Hello, world!"

Run:

romit check examples/beginner/hello_world.rmt
romit test examples/beginner/hello_world.rmt

Calculator tutorial

Open examples/beginner/calculator.rmt. The handler runs when Evaluate is sent and sets answer using words instead of symbols:

set answer to 3 times 4 plus 2

Run romit test examples/beginner/calculator.rmt.

Todo list tutorial

examples/beginner/todo_list.rmt keeps task_text and task_done, emits outputs on add and complete, and uses ordered send / expect steps inside one test.

Traffic light tutorial

examples/beginner/traffic_light.rmt combines a machine, multiple conditional on Tick handlers, and tests that send ticks between expectations.

UI framework

Planned — not implemented yet.
Stage 1 of the romit compiler does not parse or compile UI specs. This page is a design draft only. Treat examples/planned/*.rmt as syntax sketches for a future Stage 4 toolchain.

What we are aiming for

Romit’s UI layer is a declarative, component-based framework in the spirit of SwiftUI and React:

  • You describe what the screen should look like and what happens on events.
  • You do not manually wire DOM nodes or imperatively repaint the page.
  • Reactive state drives the UI: when state changes, the visible screen updates automatically (same mental model as @State in SwiftUI or useState in React).

The compiler pipeline we plan (later) is:

  1. Read the UI section of a .rmt file (views, layout, modifiers, events).
  2. Emit static HTML for structure and accessibility.
  3. Attach a small reactive JavaScript runtime that re-renders only what changed when Romit state updates.

None of that compilation exists in this repository today.

Core ideas

view components and parameters

A view is a named piece of UI. Views can take parameters (like SwiftUI’s struct properties or React props) using the same brace-free phrase style as the rest of Romit:

view Badge label text small bool is false
  text label
    size 14
    muted small

Parameters are declared on the header line; the indented block is the view body. Composition works by nesting views inside layout containers.

Reactive state

Screen-local or app-wide state is declared next to the screen (or in a dedicated state block). When a rule or handler runs set count to count plus 1, the UI layer should re-bind the text that reads count—no manual “refresh” call.

screen Counter at /counter
  state
    count int is 0

This parallels:

FrameworkReactive state
SwiftUI@State, @Observable
ReactuseState, useReducer
Romit (planned)state fields + automatic view updates

Layout containers

Nested layout is indentation-only, same as the declarative spec layer:

ContainerRole
columnVertical stack (like VStack / a flex column)
rowHorizontal stack (like HStack / a flex row)
listScrollable list of items
column
  text "Title" bold
  row
    button "Cancel" outline
    button "OK"

Elements

Basic building blocks:

ElementPurpose
textLabels and copy; can show a string or a state field
buttonTap target with a label
inputText field bound to state (planned)

Strings use "...". State references use plain names: text count shows the current value of count.

Modifiers

Modifiers are trailing words on a line (SwiftUI-style), not chained punctuation:

text "Hello" size 16 bold
text count color accent padding 12

Common modifiers in the draft: padding, color, size, bold, muted, outline, gap on containers.

You do not have to set every visual property by hand. Anything you leave out is filled in from the theme and defaults cascade (below). A plain button "Save" with no modifiers should still look like a proper button.

Events

User gestures use phrase events—no parentheses:

button "+"
  on click increment

on click increment
  set count to count plus 1

Navigation stays phrase-based too: on click go to /settings (see examples/planned/landing-dark.rmt).

App theme and built-in defaults

Romit UI is designed so beginners can skip styling until they care about branding.

An app block names the program’s UI shell:

  • home — which screen is the entry route when the app loads
  • theme — app-wide visual tokens and control styles
  • defaults — default screen settings applied to every screen unless a screen overrides them

The theme holds colours, type, spacing, corner radius, and interactive states for controls (for example button hover, button pressed, button focus, button disabled). If you do not specify something on a text, button, or container, the runtime looks it up here.

Built-in defaults sit underneath the theme: readable contrast, minimum tap targets, and sensible fonts if the app does not define a theme at all.

Cascade (who wins?)

When the compiler resolves how something should look, it walks from most specific to least:

  1. Element override — modifiers on one line, e.g. text "Hi" color red
  2. Screen defaults — the defaults block inside that screen
  3. App defaults — the defaults block directly under app (shared by all screens)
  4. App theme — tokens and per-control theme sections (button, input, …)
  5. Built-in defaults — what Romit ships when nothing else applies

Think of it like CSS inheritance with a design system: local tweak beats page layout beats design tokens beats the browser baseline.

app CounterDemo
  home Counter
  theme default
    accent #7c3aed
    button
      background accent
      text white
  defaults
    padding 20
    background #f4f4f5

A button "+" with no colour or padding lines still gets the themed button background and label colour.

Screen defaults

Each screen can rely on app-level defaults, or override them with its own defaults block. Planned screen-level keys include:

KeyPurpose
backgroundPage background (often a theme token name like background)
paddingInner padding around the main content
safeRespect safe areas (top, bottom, or top bottom)
text styleDefault typography for body copy
title styleStyle for the screen title
scrollScrolling behaviour (vertical, horizontal, none)
alignDefault alignment for the main column
layoutResponsive hint (stack, split, fluid — draft)
emptyMessage when there is no content
loadingMessage while data is loading
errorMessage when something failed

Only the keys you list need to appear; everything else falls through the cascade.

screen About at /about
  defaults
    padding 32
    background white
  column
    text "About"

Here padding and background override the app defaults; button colours still come from theme.

Planned example: theme + two screens

See examples/planned/theme_cascade.rmt:

  • Counter — button "+" has no styling modifiers; it uses theme accent colours and interaction states.
  • About — overrides defaults (padding, background) only; buttons would still use the app theme.

Example: counter (Romit draft)

Full planned example: examples/planned/counter_ui.rmt.

note PLANNED — UI counter demo. Not parsed by Stage 1 romit.

app CounterDemo
  home Counter
  theme default
    accent #2563eb
    button
      background accent
      text white
  defaults
    padding 24

  screen Counter at /
    state
      count int is 0
    column
      gap 16
      text count
        size 48
      button "+"
        on click increment
    on click increment
      set count to count plus 1
  • state: count starts at 0.
  • text count: displays the live value (you may still use size on the label; the button does not need colours).
  • button "+": no background or color lines—the theme supplies them; on click increment raises the event.
  • Handler: one line updates state; the UI runtime (future) redraws the text.

Same counter in SwiftUI and React

These snippets are the same app in today’s frameworks—useful when you learn Romit’s planned syntax.

SwiftUI

import SwiftUI

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack(spacing: 16) {
            Text("\(count)")
                .font(.largeTitle)
                .bold()
            Button("+") {
                count += 1
            }
        }
        .padding(24)
    }
}

SwiftUI keeps count in @State and rebuilds body when it changes. Romit’s draft uses an explicit state block and rules instead of a closure on the button.

React

import { useState } from "react";

export function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div style={{ padding: 24, textAlign: "center" }}>
      <p style={{ fontSize: 48, fontWeight: "bold" }}>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
}

React stores count in component state and re-renders on setCount. Romit’s planned model moves the increment into a declarative on click handler that sets spec state, closer to the event / rule style you already use in beginner .rmt files.

How compilation might work (future)

This is not built; it explains the target so tooling can align later.

flowchart LR
  subgraph source [Romit source]
    UI[app / screen / view]
    Rules[on click / state rules]
  end
  subgraph out [Output]
    HTML[HTML skeleton]
    JS[Reactive JS bundle]
  end
  UI --> HTML
  Rules --> JS
  state[(state)] --> JS
  JS --> HTML
  • HTML: semantic tags from column, row, text, button, input; modifiers become classes or inline data attributes.
  • JS: a tiny runtime subscribes to state keys (count, etc.) and updates text nodes and attributes when handlers fire.
  • Shared rules: the same romit test / interpreter ideas from Stage 1 can eventually drive both server logic and UI events.

Where to go next

  • Theme cascade + two screens: examples/planned/theme_cascade.rmt
  • Landing page draft: examples/planned/landing-dark.rmt
  • Shorter planned overview: Planned UI syntax
  • Roadmap: Roadmap (Stage 4)

When the UI compiler lands, this page will move out of “planned” and gain a tutorial that runs romit build (or similar) on counter_ui.rmt. Until then, romit check will not accept UI-only files as valid Stage 1 programs.

Brace-free surface

Romit source must not use braces, brackets, or parentheses in the surface syntax. If the lexer sees (, ), [, ], {, }, or a stray :, it reports P013:

Romit has no braces. Use indentation instead.

Allowed non-word symbols in code are limited to:

  • Operators + - * / < > and == / != (or words such as is, is not, above, below)
  • String quotes "
  • Hex colours #RRGGBB
  • Comments // or note

Calls use space-separated words, for example emit Greet with message "Hi" or set answer to 3 times 4 plus 2.

Types and collections use words instead of punctuation: List of Number, items at 0, go to /signup.

Blocks and indentation

Each block is introduced by a header line, then one or more lines indented two spaces deeper.

spec MySpec version 1

state
  count int is 0

on Tick
  rule bump
    when true
    set count to count plus 1

Validator codes

CodeMeaning
P001Tab character used for indentation
P002Odd number of spaces (use multiples of two)
P003Dedent past the outermost block
P004Indentation does not match any enclosing block
P020Parser expected an indented block

Tabs are never allowed. Mixed dedent levels must match a previous indent level on the stack.

Calls and phrases

Romit expresses calls and control flow as space-separated words instead of punctuation-heavy syntax.

Calls and assignments

Emit events and pass fields with phrases:

emit Greet with message "Hi"
set answer to 3 times 4 plus 2
send Compute with n 10

Comparisons and arithmetic can use words (is, is not, times, plus) or the allowed symbols (+, -, *, /, ==, !=).

Event headers and parameters

Handlers name their event and parameters in the same phrase style:

on Compute with n if n is 10
on Add with title
on Tick if light is red

Types and access

Types read as phrases: List of Number, items at 0, task_text is not "".

Planned UI examples use navigation phrases such as go to /signup and on click go to … instead of arrows or braces. See UI framework and Planned UI syntax.

Strings and interpolation

String interpolation does not use { or }. Use adjacent words after a string literal (the value is concatenated in order):

note documented join form (v0.3):
say "Hello" name

equivalent to building the message from "Hello " and the value of name. The longer spelling is:

"Hello " join name

Only one of these forms is required in source; this book uses say "Hello" name in examples.

Declarative layer

Stage 1 specs use the same brace-free style:

KeywordRole
specName and version
state / configFields with name type is default
entityentity Name keyed by id and indented fields
eventevent E with arg type
outputDeclared outputs with indented parameters
onHandlers: on E with args if condition
rulewhen / otherwise and set / emit / reject
machinefrom to to transitions
invariant / limit / testProperties and tests

Tests interleave send and expect lines in order:

test adds_one
  send Tick
  expect count is 1
  send Tick
  expect count is 2

Beginner programs

These programs live in the repository under examples/beginner/. Run any file with:

romit test examples/beginner/<name>.rmt

hello_world.rmt

spec HelloWorld version 1
about "Say hello — the smallest Romit program."

output Greet
  message text

event Run

on Run
  rule say_it
    when true
    emit Greet with message "Hello, world!"

test says_hello
  send Run
  expect emitted Greet with message "Hello, world!"

calculator.rmt

See the Calculator tutorial.

spec Calculator version 1
about "Multiply and add in one line — no buttons required."

state
  answer int is 0
event Evaluate

on Evaluate
  rule sample
    when true
    set answer to 3 times 4 plus 2

test two_times_three_plus_two
  send Evaluate
  expect answer is 14

fibonacci.rmt

spec Fibonacci version 1
about "Small Fibonacci answers without recursion — Stage 2 adds real functions."

state
  result int is 0

event Compute with n int

on Compute with n if n is 0
  rule zero
    when true
    set result to 0

on Compute with n if n is 1
  rule one
    when true
    set result to 1

on Compute with n if n is 10
  rule ten
    when true
    set result to 55

test fib_of_10
  send Compute with n 10
  expect result is 55

test fib_of_0
  send Compute with n 0
  expect result is 0

test fib_of_1
  send Compute with n 1
  expect result is 1

todo_list.rmt

Walkthrough: Todo list tutorial.

spec TodoList version 1
about "One task at a time: add text, then mark it done."

state
  task_text text is ""
  task_done bool is false

event Add
event MarkDone

output TaskAdded
  title text

output TaskCompleted
  title text

on Add with title
  rule create
    when not task_done or task_text is ""
    set task_text to title
    set task_done to false
    emit TaskAdded with title title

on MarkDone
  rule finish
    when task_text is not "" and not task_done
    set task_done to true
    emit TaskCompleted with title task_text

test add_then_done
  send Add with title "Buy milk"
  expect task_text is "Buy milk"
  expect emitted TaskAdded with title "Buy milk"
  send MarkDone
  expect task_done is true
  expect emitted TaskCompleted with title "Buy milk"

traffic_light.rmt

Walkthrough: Traffic light tutorial.

spec TrafficLight version 1
about "A traffic light that cycles red → green → yellow."

enum Color red green yellow

state
  light Color is red

event Tick

output ShowLight
  color text

machine light
  red to green
  green to yellow
  yellow to red

on Tick if light is red
  rule advance
    when true
    set light to green
    emit ShowLight with color green

on Tick if light is green
  rule go_yellow
    when true
    set light to yellow
    emit ShowLight with color yellow

on Tick if light is yellow
  rule go_red
    when true
    set light to red
    emit ShowLight with color red

invariant only_known_colors
  light is red or light is green or light is yellow

test red_to_green
  send Tick
  expect light is green
  expect emitted ShowLight with color green

test full_cycle
  given light is green
  send Tick
  expect light is yellow
  send Tick
  expect light is red

Advanced programs

FileTopic
refund_desk.rmtEntity state machine and refunds
garden_valve.rmtTimers and config-driven behaviour
leg_manager.rmtMany entities and limits

Advanced examples are converted to the brace-free syntax. Some tests still depend on entity runtime features that are being extended beyond the beginner interpreter.

Use romit check --no-coverage while iterating on large specs.

Planned UI syntax

See the full design draft: UI framework (planned, not implemented).

FileWhat it shows
counter_ui.rmtMinimal counter; plain "+" button uses app theme
theme_cascade.rmtApp theme, defaults, two screens (one overrides screen defaults)
landing-dark.rmtMarketing layout and navigation phrases

examples/planned/landing-dark.rmt (excerpt— not parsed by the Stage 1 compiler):

app Landing
  theme dark
    background  #0a0a0a
    text        #f5f5f5

screen Home at /
  hero
    text "Build faster with Acme" size 48 bold
    row
      button "Get started"
        on click go to /signup

Navigation uses phrases such as go to /signup and on click go to … instead of arrows or braces.

Error codes

Parser (P0xx)

CodeSummary
P001Tabs in indentation
P002Indent not a multiple of two spaces
P003Dedent beyond outermost block
P004Indent does not match a prior level
P013Braces, brackets, parens, or :
P020Expected indented block
P021Expected a word

Type and rules (E1xx, E2xx)

CodeSummary
E199Test expectation failed at runtime
E210Type or field error (checker)
E402Rule never exercised by tests (coverage)

Warnings (W4xx)

CodeSummary
W410Suspicious but legal construct

Negative fixtures live under examples/negative/.

Roadmap

  1. Stage 1 (current) — Specs, lexer, parser, checker, tests, mdBook docs, release binaries and install scripts.
  2. Stage 2 — Functions, richer expressions, replay tooling.
  3. Stage 3 — Entities and machines at full fidelity in the interpreter.
  4. Stage 4 — UI app / screen / view layer (UI framework, examples/planned/).

Distribution (planned)

  • Homebrew tap — third-party or official brew install romitlang/tap/romit once binaries are published; not implemented yet.
  • npm wrapper — global npm install -g @romitlang/cli shim that downloads the matching release; not implemented yet.