RFC — v0.8.0
Purus — /ˈpuː.rus/ — means pure in Latin. A beautiful, simple, and easy-to-use language that compiles to JavaScript. Write code without the Shift key.
- Design Principles
- File Extensions
- Lexical Structure
- 3.1 Comments
- 3.2 Identifiers
- 3.3 Reserved Keywords
- 3.4 Literals
- 3.5 Punctuation
- 3.6 Shebang
- Types and Literals
- 4.1 Numbers
- 4.2 Strings
- 4.3 String Interpolation
- 4.4 Booleans
- 4.5 Null Family
- 4.6 Regular Expressions
- 4.7 Arrays
- 4.8 Objects
- Operators
- 5.1 Operator Precedence
- 5.2 Arithmetic
- 5.3 Comparison
- 5.4 Logical
- 5.5 Nullish Coalescing
- 5.6 Pipeline
- 5.7 Optional Chaining
- 5.8 Type Check and Cast
- Declarations
- 6.1 Variable Declaration
- 6.2 Array Destructuring
- 6.3 Object Destructuring
- 6.4 Assignment
- 6.5 Type Alias
- Functions
- 7.1 Named Functions
- 7.2 No-Argument Functions
- 7.3 Multiple Parameters
- 7.4 Expression Body
- 7.5 Anonymous Functions
- 7.6 Async Functions
- 7.7 Function Calls
- 7.8 Computed Access
- 7.9 Async Function Expressions
- 7.10 Inline Callbacks
- 7.11 Type Annotations
- Control Flow
- 8.1 If / Elif / Else
- 8.2 Unless
- 8.3 Inline If (Ternary)
- 8.4 Postfix Modifiers
- 8.5 While / Until
- 8.6 For-in
- 8.7 For-range
- 8.8 Witch / Case / Default
- 8.9 Match / When (deprecated)
- 8.10 Break / Continue / Return
- Error Handling
- 9.1 Try / Catch / Finally
- 9.2 Try as Expression
- 9.3 Throw
- Modules
- 10.1 ESM Import
- 10.2 From...Import
- 10.3 Use (Dot-path Import, deprecated)
- 10.4 Export / Public
- 10.5 Namespace
- 10.6 Side-Effect Import
- 10.7 Import Attributes
- 10.8 CommonJS
- 10.9 Dynamic Import
- 10.10 Module Type Configuration
- Array Operations
- Multi-line Brackets
- Indentation and Block Structure
- Code Generation
- 14.1 Identifier Mapping
- 14.2 Type Erasure
- 14.3 Strict Mode
- 14.4 Private Fields
- Classes
- 15.1 Class Declaration
- 15.2 Constructor
- 15.3 Methods
- 15.4 Static Methods
- 15.5 Async Methods
- 15.6 Getters and Setters
- 15.7 Inheritance
- 15.8 Private Fields
- Keyword Reference Table
- Grammar Summary (EBNF-like)
Purus is designed with the following principles:
- No Shift key required. Most syntax uses lowercase keywords and
[]brackets. No(),{},<>,!,@,#,$,%,^,&,*,+,=,|,:,",',?required in Purus source. - Brackets only.
[]is the universal bracket — used for function calls, arrays, objects, grouping, and destructuring. - Keyword-based operators. All operators are English words:
add,sub,eq,and,or, etc. - Indentation-based blocks. No braces. Blocks are delimited by indentation (2 spaces recommended).
- Clean JavaScript output. Compiles to readable, idiomatic JavaScript.
- US/JIS keyboard layout friendly. The entire language can be typed without modifier keys besides Shift for uppercase (which is also rarely needed).
| Extension | JS Output | Description |
|---|---|---|
.purus |
.js |
Standard JavaScript |
.cpurus |
.cjs |
CommonJS module |
.mpurus |
.mjs |
ES Module |
Line comment — starts with --, extends to end of line:
-- this is a comment
Block comment — enclosed in ---:
--- this is a
block comment ---
Identifiers start with a letter (a-z, A-Z) or underscore (_), followed by any combination of letters, digits (0-9), hyphens (-), and underscores (_).
valid-name
my_var
_private
camelCase
data-2
Identifier normalization: Both hyphens and underscores map to _ in the JavaScript output. Therefore my-var and my_var reference the same JavaScript identifier my_var.
Caution: Do not define both
my-varandmy_varin the same scope. They will alias to the same JavaScript variable.
The following words are reserved and cannot be used as identifiers:
Declaration: const, let, var, be
Function: fn, return, to, gives, async, await
Control: if, elif, else, unless, then, while, until, for, in, range, break, continue, witch, case, match, when
Operator: add, sub, mul, div, mod, pow, neg, eq, neq, lt, gt, le, ge, and, or, not, coal, pipe
Type: is, as, of, typeof, instanceof, type
Module: import, from, export, default, require, use (deprecated), namespace, public, all
Value: true, false, null, nil, undefined, nan
Constructor: list, object
Error handling: try, catch, finally, throw
Class: class, extends, super, static, private, get, set
Other: new, delete, this
| Type | Syntax | Example |
|---|---|---|
| Integer | Decimal digits | 42, -3 |
| Float | Digits with . |
3.14, -0.5 |
| String | /// delimiters |
///hello/// |
| Interpolated string | ///...[expr].../// |
///Hello, [name]!/// |
| Regex | /pattern/flags |
/[a-z]+/gi |
| Boolean | true / false |
true |
| Null | null / nil |
null |
| Undefined | undefined |
undefined |
| NaN | nan |
NaN |
Negative number literals are recognized after these tokens: [, ,, ;, be, \, newline, indent, return, to, then, coal.
| Symbol | Name | Role |
|---|---|---|
[ |
Left bracket | Call, array, object, grouping, destructuring |
] |
Right bracket | Closing bracket |
, |
Comma | Separator (arrays, objects) |
; |
Semicolon | Separator (function args, params, destructuring) |
. |
Dot | Property access |
\. |
Optional dot | Optional chaining (?.) |
\ |
Backslash | Computed access prefix ([...]) |
.. |
Double dot | Inclusive range |
... |
Triple dot | Exclusive range |
A #! line at the beginning of a file is recognized and preserved:
#!/usr/bin/env node
const message be ///Hello///
const i be 42 -- integer
const f be 3.14 -- float
const n be -7 -- negative integer
Strings are delimited by triple slashes ///:
const greeting be ///Hello, World///
Compiles to:
const greeting = "Hello, World";Escape sequences:
| Escape | Result |
|---|---|
\n |
Newline |
\t |
Tab |
\\ |
Backslash |
\/ |
/ |
\[ |
[ (literal bracket, prevents interpolation) |
\] |
] (literal bracket) |
Embed expressions inside strings using [expr]:
const name be ///Alice///
const age be 30
const msg be ///Hello, [name]! You are [age] years old.///
Compiles to:
const name = "Alice";
const age = 30;
const msg = `Hello, ${name}! You are ${age} years old.`;Any valid Purus expression can appear inside brackets:
const result be ///[x] times 2 is [x mul 2]///
Nested brackets are correctly handled — the lexer tracks bracket depth:
const msg be ///first: [arr[0]]///
To include a literal [ or ] in a string, use \[ and \].
When a string contains no interpolated expressions, it is compiled to a regular JS string ("..."). When interpolation is present, it is compiled to a template literal (`...`).
const a be true
const b be false
const a be null
const b be nil -- alias for null
const c be undefined
const d be nan -- NaN
Both null and nil compile to JavaScript null.
nan compiles to JavaScript NaN.
Regex literals use /pattern/flags syntax:
const pattern be /[a-z]+/gi
Supported flags: g, i, m, s, u, y.
Explicit array:
const arr be [1, 2, 3]
const arr2 be [1; 2; 3] -- semicolons also work
const empty be []
Explicit constructor: list[...] is identical to [...]:
const items be list[1; 2; 3]
Range arrays: See Section 11.1.
Bracket syntax:
const obj be [name be ///Alice///, age be 30]
const empty-obj be [be] -- empty object
Explicit constructor: object[...]:
const person be object[name be ///Alice///, age be 30]
Shorthand properties:
const x be 10
const y be 20
const point be object[x, y]
-- compiles to: { x, y }
Compiles to:
const obj = { name: "Alice", age: 30 };
const emptyObj = {};
const person = { name: "Alice", age: 30 };
const point = { x, y };From lowest to highest:
| Level | Operator(s) | Description |
|---|---|---|
| 1 | pipe |
Pipeline |
| 2 | coal |
Nullish coalescing |
| 3 | or |
Logical OR |
| 4 | and |
Logical AND |
| 5 | eq / neq / not eq / is / instanceof |
Equality |
| 6 | lt / gt / le (lt eq) / ge (gt eq) |
Comparison |
| 7 | add / sub |
Addition / Subtraction |
| 8 | mul / div / mod |
Multiplication / Division / Modulo |
| 9 | pow |
Exponentiation (right-associative) |
| 10 | not / neg / typeof / await / delete / new |
Unary |
| 11 | . access / \. optional / [args] call / [\expr] access / as cast |
Postfix |
| 12 | Literals, identifiers, brackets | Primary |
| Purus | JS | Description |
|---|---|---|
a add b |
a + b |
Addition |
a sub b |
a - b |
Subtraction |
a mul b |
a * b |
Multiplication |
a div b |
a / b |
Division |
a mod b |
a % b |
Modulo |
a pow b |
a ** b |
Exponentiation |
neg x |
-x |
Unary negation |
pow is right-associative: a pow b pow c → a ** (b ** c).
| Purus | JS | Description |
|---|---|---|
a eq b |
a === b |
Strict equality |
a neq b |
a !== b |
Strict inequality |
a not eq b |
a !== b |
Alternative inequality |
a lt b |
a < b |
Less than |
a gt b |
a > b |
Greater than |
a le b |
a <= b |
Less than or equal |
a lt eq b |
a <= b |
Alternative LE |
a ge b |
a >= b |
Greater than or equal |
a gt eq b |
a >= b |
Alternative GE |
Note: eq and is are interchangeable. Both compile to ===.
Note: not eq is an alias for neq, lt eq is an alias for le, and gt eq is an alias for ge. Both forms compile to the same JavaScript output.
| Purus | JS | Description |
|---|---|---|
a and b |
a && b |
Logical AND |
a or b |
a || b |
Logical OR |
not x |
!x |
Logical NOT |
The coal operator returns the right-hand side when the left-hand side is null or undefined:
| Purus | JS |
|---|---|
a coal b |
a ?? b |
a coal b coal c |
a ?? b ?? c |
Unlike or, which treats all falsy values (false, 0, "") as false, coal only treats null and undefined as "empty":
const port be config.port coal 3000
-- uses 3000 only if port is null/undefined; 0 would be preserved
The pipe operator passes the left operand as the first argument to the right operand:
data pipe filter -- filter(data)
data pipe filter pipe map -- map(filter(data))
data pipe transform[extra-arg] -- transform(data, extra_arg)
data pipe .method[arg] -- data.method(arg)
Compilation rules:
a pipe f→f(a)a pipe f[x; y]→f(a, x, y)(prependsaas first argument)a pipe .method[x]→a.method(x)(method call ona)
The \. operator provides safe property access on potentially null/undefined values. It compiles to JavaScript's optional chaining operator ?.:
const name be user\.profile\.name
const result be obj\.method[arg]
const name = user?.profile?.name;
const result = obj?.method(arg);\. can be used for:
- Property access:
obj\.prop→obj?.prop - Method calls:
obj\.method[args]→obj?.method(args)
Combine with coal for default values:
const display be user\.name coal ///anonymous///
Type check with is / eq:
When is or eq is followed by a type name, it becomes a type check:
| Purus | JS |
|---|---|
x is string |
typeof x === "string" |
x is number |
typeof x === "number" |
x is null |
x === null |
x is MyClass |
x instanceof MyClass |
x instanceof Y |
x instanceof Y |
typeof x |
typeof x |
Primitive type names: string, number, boolean, undefined, function, symbol, bigint, null, object.
Capitalized names are treated as class constructors and use instanceof.
Type cast with as:
x as number -- erased in JS output (passes through x)
const x be 42 -- const x = 42;
let y be 10 -- let y = 10;
var z be 0 -- var z = 0; (discouraged)
Optional type annotation with of (erased in JS):
const x of Number be 42
const [a; b; c] be arr
let [first; second] be list[1; 2]
Compiles to:
const [a, b, c] = arr;
let [first, second] = [1, 2];Variable swap:
[a; b] be [b; a]
Use object[...] before be:
const object[name; age] be person
let object[host; port] be config
Compiles to:
const { name, age } = person;
let { host, port } = config;Non-declaration assignment uses be without a declaration keyword:
x be 42
obj.field be ///new value///
Compiles to:
x = 42;
obj.field = "new value";type UserId be Number
Type aliases are erased in JavaScript output.
Block body:
fn greet name
console.log[name]
function greet(name) {
console.log(name);
}Simply omit the parameter list:
fn say-hello
console.log[///Hello!///]
function say_hello() {
console.log("Hello!");
}With expression body:
fn say-hello to console.log[///Hello!///]
function say_hello() { console.log("Hello!"); }Use ; to separate parameters:
fn add a; b
return a add b
function add(a, b) {
return a + b;
}Use to for single-expression function bodies. Named functions do not have implicit return — use to return for explicit return.
fn greet name to console.log[name]
function greet(name) { console.log(name); }Explicit return with to return:
fn double x to return x mul 2
function double(x) { return x * 2; }Arrow expression:
const double be fn x to x mul 2
const double = (x) => x * 2;Arrow with no arguments:
const get-time be fn to Date.now[]
const get_time = () => Date.now();Arrow with block body:
const process be fn data
console.log[data]
return data
const process = (data) => {
console.log(data);
return data;
};async fn fetch-data url
const res be await fetch[url]
return res
async function fetch_data(url) {
const res = await fetch(url);
return res;
}Use [] instead of ():
greet[///world///] -- greet("world")
add[1; 2] -- add(1, 2)
console.log[///hello///] -- console.log("hello")
Nested calls: Use ; to separate arguments to distinguish from nested function calls:
a[b[c]; d] -- a(b(c), d)
outer[inner1[x]; inner2[y; z]] -- outer(inner1(x), inner2(y, z))
To access array elements or object properties with a computed key, use \ inside brackets to distinguish from function calls:
const item be arr[\index]
const value be obj[\key]
const first be arr[\0]
const item = arr[index];
const value = obj[key];
const first = arr[0];Without the \ prefix, brackets are interpreted as a function call: arr[index] → arr(index).
Async anonymous functions can be used as expressions:
Expression body:
const fetch-data be async fn url to await fetch[url]
const fetch_data = async (url) => await fetch(url);Block body:
const process be async fn data
const result be await transform[data]
return result
const process = async (data) => {
const result = await transform(data);
return result;
};Anonymous functions (including async) can be passed directly as arguments in function calls, enabling method chaining with callbacks:
promise.then[fn result
console.log[result]
].catch[fn err
console.error[err]
]
promise.then((result) => {
console.log(result);
}).catch((err) => {
console.error(err);
});With multi-line brackets, complex callback patterns become natural:
app.get[///path///; async fn req; res
const data be await fetch-data[]
res.json[data]
]
Type annotations are erased in JavaScript:
fn add a of Number; b of Number gives Number to a add b
of Type— parameter type annotationgives Type— return type annotation
if x lt 0
console.log[///negative///]
elif x eq 0
console.log[///zero///]
else
console.log[///positive///]
else if is also accepted as an alternative to elif.
Negated conditional:
unless done
keep-going[]
if (!(done)) {
keep_going();
}const result be if condition then 1 else 2
const result = condition ? 1 : 2;Statements can have postfix if, unless, or for:
console.log[///debug///] if verbose
console.log[///skip///] unless done
console.log[item] for item in list
if (verbose) console.log("debug");
if (!(done)) console.log("skip");
for (const item of list) console.log(item);while i lt 10
i be i add 1
until finished
do-work[]
until COND compiles to while (!(COND)).
Basic iteration:
for item in items
console.log[item]
for (const item of items) {
console.log(item);
}With index:
for i; item in items
console.log[i; item]
for (let [i, item] of items.entries()) {
console.log(i, item);
}for i in range 0; 10
console.log[i]
for (let i = 0; i < 10; i++) {
console.log(i);
}Statement form:
witch x
case 1 then ///one///
case 2 then ///two///
default ///other///
Block body in arms:
witch value
case n if n gt 0
console.log[///positive///]
default
console.log[///non-positive///]
Expression form (compiled to IIFE):
const label be witch status
case 200 then ///ok///
case 404 then ///not found///
default ///unknown///
Witch arms support:
- Literal patterns:
case 1,case ///hello///,case true - Binding patterns:
case n(binds the value ton) - Wildcard:
default(default arm, matches anything) - Guards:
case n if n gt 0(additional condition) - Body:
then EXPR(expression) or indented block
Deprecated: Use
witch/case/defaultinstead.match/whenis kept for backward compatibility.
Statement form:
match x
when 1 then ///one///
when 2 then ///two///
else ///other///
Block body in arms:
match value
when n if n gt 0
console.log[///positive///]
else
console.log[///non-positive///]
Expression form (compiled to IIFE):
const label be match status
when 200 then ///ok///
when 404 then ///not found///
else ///unknown///
Match arms support:
- Literal patterns:
when 1,when ///hello///,when true - Binding patterns:
when n(binds the value ton) - Wildcard:
else(default arm, matches anything) - Guards:
when n if n gt 0(additional condition) - Body:
then EXPR(expression) or indented block
break
continue
return
return value
try
risky[]
catch e
console.log[e]
finally
cleanup[]
The catch variable name is optional; defaults to e if omitted.
const result be try
risky[]
catch e
default-value
Compiles to an IIFE with try/catch.
throw new Error[///something went wrong///]
throw err if condition -- postfix if
import express from ///express///
import [Hono] from ///hono///
import [describe; it; expect] from ///vitest///
import axios, [AxiosError] from ///axios///
import all as fs from ///fs///
import express from "express";
import { Hono } from "hono";
import { describe, it, expect } from "vitest";
import axios, { AxiosError } from "axios";
import * as fs from "fs";The from...import syntax places the module path first, followed by the import bindings:
from ///express/// import express
from ///react/// import [useState, useEffect]
from ///fs/// import all as fs
from ///axios/// import axios, [AxiosError]
import express from "express";
import { useState, useEffect } from "react";
import * as fs from "fs";
import axios, { AxiosError } from "axios";This is equivalent to the import...from syntax in §10.1 with reversed order.
Deprecated: The
use/from...usesyntax is deprecated. Useimport...fromorfrom...importwith string paths instead.
use std.math
from std.math use sin, cos
import * as math from "std/math";
import { sin, cos } from "std/math";Dots in the path are converted to / in the import.
public fn helper to 42
public const VERSION be ///1.0///
export default fn main
console.log[///hi///]
export function helper() { return 42; }
export const VERSION = "1.0";
export default function main() {
console.log("hi");
}namespace utils
fn helper to 42
const utils = (() => {
function helper() { return 42; }
})();Compiles to an IIFE (Immediately Invoked Function Expression).
Import a module purely for its side effects (e.g., polyfills, configuration):
import ///dotenv/config///
import ///./setup///
import "dotenv/config";
import "./setup";No bindings are introduced — the module is simply executed.
Import attributes allow specifying additional metadata for module imports using the with keyword:
import package from ///./package.json/// with [ type be ///json/// ]
import [name; version] from ///./package.json/// with [ type be ///json/// ]
import package from "./package.json" with { type: "json" };
import { name, version } from "./package.json" with { type: "json" };The with clause uses Purus's bracket syntax [ key be value ], which compiles to JavaScript's with { key: value }. Multiple attributes can be separated by ; or ,.
const fs be require[///fs///]
const fs = require("fs");Dynamic imports are supported through standard function call syntax:
const mod be await import[///./module.js///]
By default, .purus files compile as ES Modules (ESM). This can be configured to CommonJS via --type CLI option, config.purus, or package.json.
Resolution order (highest priority first):
- CLI:
purus build --type commonjs config.purus:const type be ///commonjs///package.json:{ "type": "commonjs" }- Default:
module(ESM)
Values match package.json's type field: module or commonjs.
CommonJS output examples:
import express from ///express///
import [Hono] from ///hono///
import all as fs from ///fs///
import ///dotenv/config///
const express = require("express");
const { Hono } = require("hono");
const fs = require("fs");
require("dotenv/config");public const VERSION be ///1.0///
export default 42
const VERSION = "1.0";
exports.VERSION = VERSION;
module.exports = 42;File extension overrides: .cpurus → always CJS, .mpurus → always ESM, regardless of configuration.
Generate arrays from numeric ranges:
const inclusive be [0..5] -- [0, 1, 2, 3, 4, 5]
const exclusive be [0...5] -- [0, 1, 2, 3, 4]
Compiles to Array.from:
const inclusive = Array.from({ length: 5 - 0 + 1 }, (_, i) => i + 0);
const exclusive = Array.from({ length: 5 - 0 }, (_, i) => i + 0);..— inclusive (both start and end included)...— exclusive (end excluded)
Extract a portion of an array:
const middle be numbers[\2..4] -- numbers.slice(2, 5) — inclusive
const partial be numbers[\1...4] -- numbers.slice(1, 4) — exclusive
Assign to a slice to replace elements:
numbers[\2..4] be [///a///; ///b///; ///c///]
numbers.splice(2, 4 - 2 + 1, "a", "b", "c");Bracket expressions (function calls, arrays, objects) can span multiple lines. Newlines, indentation, and comments inside brackets are treated as whitespace:
Multi-line array:
const items be [
///apple///,
///banana///,
///cherry///
]
Multi-line function call:
server.listen[
port;
fn to console.log[///started///]
]
Multi-line object:
const config be object[
host be ///localhost///,
port be 3000,
debug be true
]
This enables naturally readable code for complex data structures and function calls with multiple arguments or inline callbacks.
Purus uses indentation-based block structure (off-side rule):
- Blocks are introduced by keywords like
fn,if,else,elif,while,for,match,when,try,catch,finally,namespace. - The block body must be indented more than the introducing keyword.
- The block ends when indentation returns to the parent level.
- Recommended indentation: 2 spaces.
- Tabs are treated as 2 spaces.
fn example x
if x gt 0 -- block level 1
console.log[///pos///] -- block level 2
else
console.log[///neg///] -- block level 2
All hyphens in Purus identifiers are replaced with underscores in JavaScript output:
| Purus | JavaScript |
|---|---|
my-variable |
my_variable |
my_variable |
my_variable |
get-user-name |
get_user_name |
The following constructs are removed during code generation:
of Type— parameter type annotationsgives Type— return type annotationsas Type— type casts (the expression value is preserved)type Name be Type— type alias declarations
By default, the Purus compiler emits "use strict"; at the top of every generated JavaScript file. This can be controlled via:
CLI flag:
purus build --strict true -- enables strict mode (default)
purus build --strict false -- disables strict mode
Configuration file (config.purus):
const strict be true -- enables strict mode (default)
const strict be false -- disables strict mode
The output module format for .purus files can be configured. Values are the same as package.json's type field.
CLI flag:
purus build --type module -- ES Modules (default)
purus build --type commonjs -- CommonJS
Configuration file (config.purus):
const type be ///module/// -- ES Modules (default)
const type be ///commonjs/// -- CommonJS
Resolution order: CLI --type > config.purus type > package.json type > default (module).
File extension overrides: .cpurus always produces CJS, .mpurus always produces ESM.
When strict mode is enabled, the generated output begins with:
"use strict";The CLI flag takes precedence over the configuration file setting.
Inside a class body, fields declared with private are tracked by the compiler. When any dot access (e.g., this.field-name) references a private field name, the compiler automatically emits a # prefix in the JavaScript output:
| Purus | JavaScript |
|---|---|
this.secret (private) |
this.#secret |
this.name (public) |
this.name |
This mapping is scoped to the enclosing class — private field names from one class do not affect another.
Purus supports JavaScript class declarations with an indentation-based syntax.
class Animal
fn new[name]
this.name be name
fn speak
console.log[this.name]
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name);
}
}Constructors are declared with fn new. Parameters use [] brackets with ; separators:
class Point
fn new[x; y]
this.x be x
this.y be y
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}Expression body with to:
class Wrapper
fn new[value] to this.value be value
class Wrapper {
constructor(value) { this.value = value; }
}Methods use the same fn syntax as regular functions:
class Calculator
fn add a; b to a add b
fn multiply a; b
return a mul b
class Calculator {
add(a, b) { return a + b; }
multiply(a, b) {
return a * b;
}
}Prefix method declarations with static:
class MathUtils
static fn square x to x mul x
static fn cube x
return x pow 3
class MathUtils {
static square(x) { return x * x; }
static cube(x) {
return x ** 3;
}
}Prefix method declarations with async:
class Api
async fn fetch-data url
const res be await fetch[url]
return res.json[]
class Api {
async fetch_data(url) {
const res = await fetch(url);
return res.json();
}
}Static async methods combine both prefixes:
class Service
static async fn load to await fetch[///data///]
class Service {
static async load() { return await fetch("data"); }
}Use get fn and set fn to declare accessors:
class Person
fn new[name]
this.internal-name be name
get fn name to this.internal-name
set fn name value
this.internal-name be value
class Person {
constructor(name) {
this.internal_name = name;
}
get name() { return this.internal_name; }
set name(value) {
this.internal_name = value;
}
}Use extends to inherit from a parent class. Use super to call the parent constructor or methods:
class Animal
fn new[name]
this.name be name
fn speak
console.log[this.name]
class Dog extends Animal
fn new[name; breed]
super[name]
this.breed be breed
fn speak
super.speak[]
console.log[///Woof!///]
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(this.name);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name);
this.breed = breed;
}
speak() {
super.speak();
console.log("Woof!");
}
}Use private to declare private fields. Private fields are prefixed with # in JavaScript output:
class Account
private balance be 0
fn new[initial]
this.balance be initial
fn deposit amount
this.balance be this.balance add amount
get fn balance to this.balance
class Account {
#balance = 0;
constructor(initial) {
this.#balance = initial;
}
deposit(amount) {
this.#balance = this.#balance + amount;
}
get balance() { return this.#balance; }
}Private fields without a default value:
class Secret
private data
class Secret {
#data;
}| Keyword | JS Output | Description |
|---|---|---|
const |
const |
Constant declaration |
let |
let |
Variable declaration |
var |
var |
Var declaration (discouraged) |
be |
= |
Assignment operator |
| Keyword | JS Output | Description |
|---|---|---|
fn |
function / => |
Function declaration/expression |
return |
return |
Return value |
to |
{ expr; } / => expr |
Expression body |
to return |
{ return expr; } |
Explicit return expression body |
gives |
(erased) | Return type annotation |
async |
async |
Async function modifier |
await |
await |
Await expression |
| Keyword | JS Output | Description |
|---|---|---|
if |
if |
Conditional |
elif |
else if |
Else-if branch |
else |
else |
Else branch |
unless |
if (!(...)) |
Negated conditional |
then |
(ternary) | Inline conditional |
| Keyword | JS Output | Description |
|---|---|---|
while |
while |
While loop |
until |
while (!(...)) |
Negated while |
for |
for |
For loop |
in |
of / in |
Iterator keyword |
range |
(numeric range) | Range-based loop |
break |
break |
Break out of loop |
continue |
continue |
Continue to next iteration |
| Keyword | JS Output | Description |
|---|---|---|
witch |
if-else chain / IIFE | Witch expression/statement |
case |
(witch arm) | Witch case |
default |
(witch default) | Default arm |
match |
if-else chain / IIFE | Match expression/statement (deprecated) |
when |
(match arm) | Match case (deprecated) |
| Keyword | JS Output | Description |
|---|---|---|
import |
import |
ESM import |
import ///mod/// |
import "mod" |
Side-effect import |
from |
from |
Import source |
export |
export |
ESM export |
default |
default |
Default export |
require |
require() |
CommonJS require |
use |
import |
Dot-path import (deprecated) |
namespace |
IIFE | Module namespace |
public |
export |
Public export |
all |
* as |
Namespace import |
| Keyword | JS Output |
|---|---|
add |
+ |
sub |
- |
mul |
* |
div |
/ |
mod |
% |
pow |
** |
neg |
- (unary) |
| Keyword | JS Output |
|---|---|
eq |
=== |
neq / not eq |
!== |
lt |
< |
gt |
> |
le / lt eq |
<= |
ge / gt eq |
>= |
| Keyword | JS Output |
|---|---|
and |
&& |
or |
|| |
not |
! |
| Keyword | JS Output |
|---|---|
coal |
?? |
| Keyword | JS Output |
|---|---|
pipe |
f(a) |
| Keyword | JS Output | Description |
|---|---|---|
is |
=== |
Equality check (alias of eq) |
as |
(erased) | Type cast |
of |
(erased) | Type annotation |
typeof |
typeof |
Typeof operator |
instanceof |
instanceof |
Instance check |
type |
(erased) | Type alias |
| Keyword | JS Output | Description |
|---|---|---|
class |
class |
Class declaration |
extends |
extends |
Class inheritance |
super |
super |
Parent class reference |
static |
static |
Static method modifier |
private |
# (prefix) |
Private field declaration |
get |
get |
Getter accessor |
set |
set |
Setter accessor |
| Keyword | JS Output | Description |
|---|---|---|
new |
new |
Constructor |
delete |
delete |
Delete property |
this |
this |
This reference |
throw |
throw |
Throw exception |
try |
try |
Try block |
catch |
catch |
Catch block |
finally |
finally |
Finally block |
list |
[…] |
Array literal |
object |
{…} |
Object literal |
null |
null |
Null value |
nil |
null |
Null alias |
undefined |
undefined |
Undefined value |
nan |
NaN |
NaN value |
| Symbol | JS Output | Description |
|---|---|---|
\. |
?. |
Optional chaining |
\ |
(computed prefix) | Computed access marker |
. |
. |
Property access |
.. |
(inclusive range) | Inclusive range |
... |
(exclusive range) | Exclusive range |
Program = { Statement } ;
Statement = VarDecl | FnDecl | ClassDecl | IfStmt | UnlessStmt
| WhileStmt | UntilStmt | ForStmt | WitchStmt | MatchStmt
| TryCatch | Throw | Return | Break | Continue
| ImportDecl | FromImportDecl | UseDecl | ModDecl | ExportDecl | PublicDecl
| TypeDecl | DeleteStmt
| Expr "be" Expr (* assignment *)
| Expr (* expression statement *)
;
VarDecl = ("const" | "let" | "var")
( "object" "[" IdentList "]" "be" Expr (* object destructuring *)
| "[" IdentList "]" "be" Expr (* array destructuring *)
| Ident ["of" Type] "be" Expr (* simple binding *)
)
[PostfixMod] ;
IdentList = Ident { (";" | ",") Ident } ;
FnDecl = ["async"] "fn" [Ident] ParamList ["gives" Type]
( "to" ["return"] Expr | INDENT Block DEDENT ) ;
ParamList = { Ident ["of" Type] ";" } [Ident ["of" Type]] ;
IfStmt = "if" Expr (INDENT Block DEDENT | "then" Expr "else" Expr)
{ ("elif" | "else" "if") Expr INDENT Block DEDENT }
[ "else" INDENT Block DEDENT ] ;
UnlessStmt = "unless" Expr INDENT Block DEDENT ;
WhileStmt = "while" Expr INDENT Block DEDENT ;
UntilStmt = "until" Expr INDENT Block DEDENT ;
ForStmt = "for" Ident [";" Ident] "in"
( "range" Primary ";" Primary
| Expr
) INDENT Block DEDENT ;
WitchStmt = "witch" Expr INDENT { WitchArm } DEDENT ;
WitchArm = "case" Pattern ["if" Expr]
( "then" Expr | INDENT Block DEDENT )
| "default" ( Expr | INDENT Block DEDENT )
;
MatchStmt = "match" Expr INDENT { MatchArm } DEDENT ;
MatchArm = "when" Pattern ["if" Expr]
( "then" Expr | INDENT Block DEDENT )
| "else" ( Expr | INDENT Block DEDENT )
;
Pattern = IntLit | FloatLit | StrLit | BoolLit | "null" | "nil" | Ident ;
TryCatch = "try" INDENT Block DEDENT
"catch" [Ident] INDENT Block DEDENT
["finally" INDENT Block DEDENT] ;
ImportDecl = "import" String (* side-effect import *)
| "import" ("all" "as" Ident | "[" IdentList "]" | Ident ["," "[" IdentList "]"])
"from" String ;
FromImportDecl = "from" String "import"
("all" "as" Ident | "[" IdentList "]" | Ident ["," "[" IdentList "]"])
;
UseDecl = "use" DottedName (* deprecated *)
| "from" DottedName "use" Ident { "," Ident } (* deprecated *)
;
ModDecl = "namespace" Ident INDENT Block DEDENT ;
ClassDecl = "class" Ident ["extends" Ident]
INDENT { ClassMember } DEDENT ;
ClassMember = "private" Ident ["be" Expr] (* private field *)
| "fn" "new" ["[" ParamList "]"]
( "to" Expr | INDENT Block DEDENT ) (* constructor *)
| ["static"] ["async"] "fn" Ident ParamList ["gives" Type]
( "to" Expr | INDENT Block DEDENT ) (* method *)
| "get" "fn" Ident ["gives" Type]
( "to" Expr | INDENT Block DEDENT ) (* getter *)
| "set" "fn" Ident Ident
( "to" Expr | INDENT Block DEDENT ) (* setter *)
;
PostfixMod = "if" Expr | "unless" Expr | "for" Ident "in" Expr ;
Expr = Pipe ;
Pipe = Coal { "pipe" Coal } ;
Coal = Or { "coal" Or } ;
Or = And { "or" And } ;
And = Equality { "and" Equality } ;
Equality = Comparison { ("eq" | "neq" | "not" "eq" | "is" | "instanceof") Comparison } ;
Comparison = Addition { ("lt" ["eq"] | "gt" ["eq"] | "le" | "ge") Addition } ;
Addition = Multiplication { ("add" | "sub") Multiplication } ;
Multiplication = Power { ("mul" | "div" | "mod") Power } ;
Power = Unary [ "pow" Power ] ; (* right-associative *)
Unary = ("not" | "neg" | "typeof" | "await" | "new") Unary | Postfix ;
Postfix = Primary { "." Ident ["[" ArgList "]"]
| "\." Ident ["[" ArgList "]"]
| "[" ArgList "]"
| "[\\" Expr "]"
| "as" Ident } ;
Primary = IntLit | FloatLit | StrLit | InterpStr | Regex
| "true" | "false" | "null" | "nil" | "undefined" | "this" | "super"
| Ident
| "list" "[" ExprList "]"
| "object" "[" ObjEntries "]"
| "[" BracketExpr "]"
| "fn" ParamList ("to" Expr | INDENT Block DEDENT)
| "async" "fn" ParamList ("to" Expr | INDENT Block DEDENT)
| "if" Expr "then" Expr "else" Expr
| "match" Expr INDENT { MatchArm } DEDENT
| "try" INDENT Block DEDENT "catch" [Ident] INDENT Block DEDENT
;
BracketExpr = (* empty → empty array *)
| "be" "]" (* empty object *)
| Expr ".." Expr (* inclusive range *)
| Expr "..." Expr (* exclusive range *)
| Expr "be" Expr { ("," | ";") Expr "be" Expr } (* object *)
| Expr { "," Expr } (* array *)
| Expr { ";" Expr } (* array / args *)
| Expr (* grouping *)
;
ArgList = Expr { (";" | ",") Expr } ;
ExprList = Expr { (";" | ",") Expr } ;
ObjEntries = Ident ["be" Expr] { ("," | ";") Ident ["be" Expr] } ;
DottedName = Ident { "." Ident } ;Purus is licensed under the Apache 2.0 License.