/ Documentation
v0.1.0 — Language Reference

Ometer Docs

Ometer is a dynamically typed, web-first programming language with a custom syntax, bytecode compiler, and its own virtual machine — built entirely in TypeScript.

Installation

Ometer requires Node.js 18+. No global installs needed — everything runs through ts-node.

bash# Clone and enter
git clone https://github.com/Ometer/ometer.git && cd ometer

# Install deps
npm install

# Verify
npx ts-node src/cli.ts help

Hello World

hello.omfn greet(name) {
  return "Hello, " + name + "!"
}

print(greet("World"))
terminalnpx ts-node src/cli.ts run hello.om
→ Hello, World!

CLI Reference

Command Description
ometer run <file> Execute an Ometer source file end-to-end
ometer lex <file> Print the token stream (debug)
ometer parse <file> Print the AST as JSON (debug)
ometer compile <file> Print bytecode disassembly (debug)
ometer help Show usage information

Syntax Overview

Ometer uses curly-brace blocks, C-style operators, and its own keyword set. Files use the .om extension. Comments use // or /* */.

// Single-line comment
/* Multi-line
   comment */

let x = 42
let name = "Ometer"
let active = true
let nothing = null

Variables

Declared with let. Dynamically typed — can hold any value and be reassigned to a different type.

let count = 0
let label = "hello"

// Compound assignment
count += 1   // 1
count -= 1   // 0
count *= 3   // 0
count /= 2   // 0

Types

Type Literal Notes
Number 42 / 3.14 Integer and float, stored as JS number
String "hi" / 'hi' Single or double quotes. Escape sequences: \n \t \\ \" \'
Bool true / false Boolean literals
Null null Intentional absence of value
Array [1, "x", true] Ordered, any element types
Object { key: val } String keys, any value types
Function fn(x) { ... } First-class values

Operators

Arithmetic

x + y   // add / string concatenation
x - y   // subtract
x * y   // multiply
x / y   // divide
x % y   // modulo
x ** y  // power (right-associative)
-x      // unary negation

Comparison

x == y   x != y          // equality
x < y    x > y    x <= y    x >= y    // comparison

Logical

x && y   // AND — short-circuits, returns actual value
x || y   // OR  — short-circuits, returns actual value
!x       // NOT

Control Flow

if / else if / else

if (score >= 90) {
  print("A")
} else if (score >= 70) {
  print("B")
} else {
  print("C")
}

while

let i = 0
while (i < 5) {
  print(i)
  i += 1
}

for-in

let fruits = ["apple", "banana", "cherry"]
for (fruit in fruits) {
  print(fruit)
}

// Works on objects too — iterates over keys
let user = { name: "Om", age: 1 }
for (key in user) {
  print(key)
}

break / continue

while (true) {
  if (done) { break }
  if (skip) { continue }
}

Functions

Functions are first-class values. Named with fn name(params) or anonymous as expressions.

// Named declaration
fn add(a, b) {
  return a + b
}

// Anonymous expression
let double = fn(x) { return x * 2 }

// Higher-order
fn apply(f, val) {
  return f(val)
}
print(apply(double, 21))  // 42

// Closures
fn counter() {
  let n = 0
  return fn() { n += 1; return n }
}
let c = counter()
print(c())  // 1
print(c())  // 2

Arrays

let nums = [1, 2, 3]

print(nums[0])       // 1
print(nums.length)   // 3

nums.push(4)
nums.pop()
nums.reverse()

let joined = nums.join(", ")
let found = nums.includes(2)
let part = nums.slice(0, 2)

Objects

let user = {
  name: "Ometer",
  version: 1,
  active: true
}

print(user.name)       // Ometer
print(user["version"]) // 1

user.active = false
user["score"] = 99

Imports

import web   // loads the web stdlib module

let app = web.create()

Built-in Modules

math

print(math.floor(3.7))   // 3
print(math.sqrt(16))     // 4
print(math.abs(-5))      // 5
print(math.random())     // 0..1
print(math.pi)           // 3.14159...

str

print(str.upper("hello"))          // HELLO
print(str.split("a,b,c", ","))    // [a, b, c]
print(str.replace("hi", "h", "H")) // Hi
print(str.num("42"))               // 42

String methods (instance)

let s = "Hello Ometer"
s.upper()       // HELLO OMETER
s.lower()       // hello ometer
s.trim()
s.split(" ")    // ["Hello", "Ometer"]
s.includes("Om")
s.slice(0, 5)   // Hello
s.length        // 12

web.create()

Creates and returns a new Ometer web application. Must import web first.

import web
let app = web.create()

Routing

app.get("/users", fn(req, res) {
  res.json([])
})

app.post("/users", fn(req, res) {
  res.status(201).json({ created: true })
})

app.put("/users/:id", fn(req, res) {
  let id = req.params.id
  res.json({ updated: id })
})

app.delete("/users/:id", fn(req, res) {
  res.json({ deleted: req.params.id })
})

Middleware

Middleware receives (req, res, next). Call next() to pass to the next handler.

app.use(fn(req, res, next) {
  print(req.method + " " + req.path)
  next()
})

Request Object

Property Type Description
req.method String HTTP method: GET, POST, PUT, DELETE
req.path String URL path (no query string)
req.url String Full URL including query string
req.body Object Parsed JSON request body
req.params Object URL parameters (e.g. :id)
req.query Object Parsed query string ?key=val
req.headers Object HTTP request headers

Response Object

Method Description
res.send(text) Send plain text (Content-Type: text/plain)
res.json(obj) Send JSON response (Content-Type: application/json)
res.html(str) Send HTML response (Content-Type: text/html)
res.status(code) Set HTTP status code — chainable: res.status(404).json({...})
res.redirect(url) Send 302 redirect to URL

app.listen(port)

app.listen(3000)
// → 🚀 Ometer server running → http://localhost:3000

Compilation Pipeline

.om source → Lexer → Tokens → Parser → AST → Compiler → Bytecode → VM → Output

Each stage is inspectable via the CLI debug commands. The pipeline is strictly linear — no optimization passes in v0.1.

Bytecode Opcodes

PUSH
<value>
Push constant onto stack
POP
Discard top of stack
DUP
Duplicate top of stack
LOAD / STORE / DEFINE
<name>
Variable read, write, declare
ADD / SUB / MUL / DIV
Binary arithmetic
MOD / POW / NEG / NOT
Modulo, power, unary operators
EQ / NEQ / LT / GT / LTE / GTE
Comparison — pushes bool result
JUMP
<idx>
Unconditional jump
JUMP_IF_FALSE
<idx>
Jump if top of stack is falsy
JUMP_IF_TRUE
<idx>
Used for short-circuit ||
MAKE_FN
<name> <arity> <chunk>
Push a function value
CALL
<argCount>
Call function below args on stack
RETURN
Return top of stack from frame
MAKE_ARRAY
<count>
Pop N items, push array
MAKE_OBJECT
<count>
Pop N key-value pairs, push object
GET_MEMBER / SET_MEMBER
<prop>
Dot access on objects
GET_INDEX / SET_INDEX
Bracket access on arrays/objects
ITER_INIT / ITER_NEXT
for-in loop iteration
PRINT
Pop and write to stdout
IMPORT
<module>
Load stdlib module onto stack

VM Architecture

The VM is stack-based with a linked call-frame system. Each function call creates a new Frame with its own instruction pointer, value stack, and Scope. Scopes are linked-list environments — variable lookup walks up the chain until the global scope.

// Call frame structure:
Frame {
  chunk:  Chunk    // compiled bytecode
  ip:     number   // instruction pointer
  scope:  Scope    // linked environment
  stack:  Value[]  // value stack
}

// Scope chain:
globalScope → functionScope → blockScope

Keywords

Keyword Purpose
let Declare a variable
fn Declare a named or anonymous function
return Return a value from a function
if / else Conditional branches
while Loop while condition is truthy
for / in Iterate over arrays or objects
break / continue Loop control
import Load a built-in module
print Write value to stdout
true / false Boolean literals
null Null value

Error Messages

⚠ Tip

All errors include a line number. Run ometer lex or ometer parse to isolate which stage failed.

Error Cause
LexerError: Unexpected character Invalid character in source
LexerError: Unterminated string Missing closing quote
ParseError: Expected '(' Missing paren after if/while/fn
ParseError: Invalid assignment target Left side of = is not assignable
CompileError: 'break' outside loop break/continue not inside a loop
RuntimeError: Undefined variable Variable used before declaration
RuntimeError: Division by zero Dividing by 0 at runtime
RuntimeError: Cannot call non-function Calling a non-callable value

Grammar (BNF)

program       → statement* EOF
statement     → letDecl | fnDecl | returnStmt | ifStmt
              | whileStmt | forInStmt | importStmt
              | printStmt | breakStmt | continueStmt | exprStmt
letDecl       → "let" IDENT ("=" expression)?
fnDecl        → "fn" IDENT "(" params ")" block
returnStmt    → "return" expression?
ifStmt        → "if" "(" expr ")" block ("else" (ifStmt | block))?
whileStmt     → "while" "(" expr ")" block
forInStmt     → "for" "(" IDENT "in" expr ")" block
expression    → assignment
assignment    → logicalOr (("=" | "+=" | "-=" | "*=" | "/=") assignment)?
logicalOr     → logicalAnd ("||" logicalAnd)*
logicalAnd    → equality ("&&" equality)*
equality      → comparison (("==" | "!=") comparison)*
comparison    → addition (("<" | ">" | "<=" | ">=") addition)*
addition      → multiplication (("+" | "-") multiplication)*
multiplication→ power (("*" | "/" | "%") power)*
power         → unary ("**" unary)*
unary         → ("!" | "-") unary | postfix
postfix       → primary (call | "." IDENT | "[" expr "]")*
primary       → NUMBER | STRING | BOOL | NULL | IDENT
              | "fn" "(" params ")" block
              | "[" (expr ("," expr)*)? "]"
              | "{" (IDENT ":" expr ("," IDENT ":" expr)*)? "}"
              | "(" expr ")"