Quick start
Build from the repository root with ./build.sh on macOS/Linux or
build.bat on Windows. With no arguments, either script writes the executable to the
repository root as openscript or openscript.exe. The optional
--all builds compressed releases under dist/compressed/ for supported
Windows, Linux, macOS, BSD, and WebAssembly targets (js/wasm and
wasip1/wasm). Each archive has its own .sha256 checksum file, and the
JS/WASM package includes Go's Node.js runtime files.
Scripts use the .os extension. The executable accepts a script path with -f
or -file. The built-in I/O module provides println.
import { println } from "core:io";
const greeting = "Hello, OpenScript!";
println(greeting);
./openscript -f hello.os
# Windows: .\openscript.exe -f .\hello.os
Statements generally end in semicolons. Blocks use braces. Whitespace and comments separate tokens but are otherwise insignificant.
Lexical rules
Identifiers
Identifiers may use Unicode letters, letter numbers, combining marks, decimal digits after the first
character, connector punctuation, and underscore. Keywords are reserved unless a construct
explicitly accepts a keyword name, such as a member accessed with ->.
Comments and whitespace
// Line comment
/* Block comment
may span lines */
Unicode whitespace and common Unicode line separators are recognized. Line breaks are not statement terminators; semicolons are used explicitly.
Strings and numbers
Strings may use single or double quotes and cannot contain an unescaped line break. Recognized
escapes include \a, \b, \f, \n, \r,
\t, \v, \0, escaped quotes and backslash, \xNN,
\uNNNN, \UNNNNNNNN, and braced Unicode \u{...}.
Numeric literals include decimal integers and fractions, exponent notation, and hexadecimal
(0x), octal (0o), and binary (0b) prefixes. Underscores are
accepted in digit runs.
Values and literals
The language includes number, BigInt, string, boolean, null, array, object, function, class, and
promise values. BigInt literals use an n suffix and support exact integer arithmetic.
Boolean and null literals are true, false, and null.
var enabled = true;
var absent = null;
var ratio = 12.5e-2;
var large = 12345678901234567890n;
var mask = 0xff;
Arrays use square brackets. Objects use braces and string or identifier keys:
var colors = ["red", "green", "blue"];
var person = { name: "Ada", "role": "engineer" };
Expressions
Expressions support literals, identifiers, grouping, function and class expressions, calls, property/index access, unary and binary operators, assignments, ternaries, await, and switch expressions.
Operator precedence
From highest to lowest, according to the parser's expression chain:
| Level | Operators / forms | Associativity |
|---|---|---|
| Postfix | Call (), member ->, index [], postfix
++ --
|
Left to right |
| Unary | + - ! ~ prefix ++
-- await
|
Right to left |
| Multiplicative | * / % |
Left to right |
| Additive | + - |
Left to right |
| Shift | << >> |
Left to right |
| Relational | < <= > >= |
Left to right |
| Equality | == != |
Left to right |
| Bitwise AND / XOR / OR | & then ^ then | |
Left to right |
| Logical AND / OR | && then || |
Left to right |
| Assignment | =, compound assignment operators |
Right to left |
| Ternary | condition ? thenExpr : elseExpr |
Right-associative branches |
Compound assignments are *=, /=, %=, +=,
-=, <<=, >>=, &=,
^=, and |=.
Logical operators short-circuit. == and != use runtime value equality;
switch matching uses the runtime's case-equality operation.
var score = 8;
var message = score >= 6 && score < 10
? "pass"
: "retry";
score += 1;
score++;
var next = score--;
Access and calls
person->name = "Grace";
person["name"];
colors[0] = "amber";
printValue(person->name);
Property access uses ->; bracket access accepts an expression as a key/index. Calls
use parenthesized comma-separated arguments.
Variables and patterns
Declarations
var globalCount = 0;
let localName = "Ada";
const limit = 100;
var first = 1, second = 2;
var is restricted to global/module scopes. let is not allowed at global
scope. const is supported at both. Compiler checks also reject duplicate names in the
same scope and assignment to constants.
Array destructuring
const [red, green, blue] = colors;
Object destructuring
const { name, role: jobTitle } = person;
Object patterns may bind a property under its original name or rename it with
property: localName. Destructuring is available with var,
let, and const, subject to the same scope rules as ordinary declarations.
Functions
Function expressions use fn. Named function declarations use the top-level declaration
form with ::.
var add::fn(left, right) {
return left + right;
};
var increment = fn(value) {
return value + 1;
};
var anonymous = fn() {
return null;
};
Functions support positional parameters, return statements, closures over outer variables, and
variadic parameters marked with .... A variadic parameter collects remaining arguments
into an array.
var collect::fn(prefix, values...) {
println(prefix);
println(values);
};
collect("items", 2, 3, 4);
A function without a returned value yields null. A return may omit its expression, which
also returns null.
Async functions and await
var fetchValue = fn() async {
return 42;
};
var result = await fetchValue();
Calling an async function produces a promise. await is accepted only inside an async
function and suspends until the awaited promise settles.
Arrays and objects
Array literals accept expressions and spread elements. Object literals accept key/value entries and object spread.
var more = [0, ...colors, "violet"];
var profile = {
name: "Ada",
...person,
active: true
};
Arrays support numeric indexing and assignment. Objects support property access with
-> or bracket notation, and property assignment. Missing object properties read as
null; array bounds violations are runtime errors.
more[0] = 10;
profile->active = false;
profile["name"];
Arrays expose ->len(), ->map(callback),
->filter(callback), and ->find(callback). Each callback receives
the element, its zero-based index, and the source array. map returns transformed
values, filter retains values whose callback result is truthy, and find
returns the first matching value or null.
Objects expose ->map(callback), ->filter(callback), and
->find(callback). Their callbacks receive the value, key, and source object;
map and filter preserve keys, while find returns the first
matching value or null.
Strings expose ->len(), ->contains(text),
->startsWith(text), ->endsWith(text),
->indexOf(text), ->toLower(), ->toUpper(),
->trim(), ->split(separator),
->replace(search, value), and ->replaceAll(search, value).
len and indexOf use UTF-8 byte counts and offsets; use
core:unicode for code-point operations. Case conversion also has
toLowerCase and toUpperCase aliases.
var values = [3, 8, 11];
var doubled = values->map(fn(value, index) {
return value * 2 + index;
});
var firstLarge = values->find(fn(value) {
return value > 5;
});
Classes
Classes may define a constructor and methods. When omitted, an empty constructor is
supplied. A class can extend one named base class. Instances are created by calling the class
name.
var Animal::class {
constructor: fn(name) {
this->name = name;
},
speak: fn() {
return "...";
}
};
var Dog::class(Animal) {
constructor: fn(name) {
super(name);
this->kind = "dog";
},
speak: fn() {
return "woof";
}
};
var pet = Dog("Milo");
pet->speak();
this refers to the instance inside a constructor or method. super(...)
invokes the base constructor. Methods are inherited through the base-class chain; instance fields
are assigned through this->field.
Control flow
Conditional statements
if (ready) {
start();
} else {
wait();
}
The body may be a braced block or a single block-level statement.
log expression; is a dedicated statement that writes the expression through the
runtime's standard output path.
log "diagnostic value";
Loops
loop supports a while-style condition and a three-part for-style form. The for-style
initializer may be a declaration or expression; its condition and update are separated by
semicolons.
var index = 0;
loop (index < 3) {
println(index);
index++;
}
loop (let i = 0; i < 3; i++) {
println(i);
}
loop (;;) {
break;
}
An omitted for-style condition is treated as an unconditional loop. break; exits the
nearest loop or switch. continue; inside a loop starts its next iteration. Inside a
switch case, continue advances to the next case body; a nested loop takes precedence.
Switch
Statement switch
A statement switch compares its selector to each label, supports multiple comma-separated values per
case, and permits one optional default in any position. Cases execute in source order
after dispatch: they do not automatically break. Empty cases and ordinary case completion fall
through. Use break; to exit the switch.
switch (value) {
case 0:
case 1, 2, 3:
println("small");
break;
case 4:
println("four");
continue; // continue with the next case body
default:
println("other");
}
break exits the switch.
continue skips the rest of the current case and jumps to the following case body; from
the final case it exits the switch. A loop nested inside a case handles its own break/continue
first.
Switch expression
Switch expressions produce one value and require a default alternative. Their alternatives use commas
and =>; each result is an expression.
var label = switch (status) {
200, 201 => "success",
404 => "missing",
default => "other"
};
Unlike statement switches, switch expressions do not have case bodies with statements or fall-through.
Exceptions
throw expression; throws any value, including strings, numbers, arrays, objects, and
Error instances. An optional catch(name) binds the original thrown value
to a local name. Runtime-generated errors are caught as Error objects with
message and line properties.
try {
throw "configuration is missing";
} catch (err) {
println(err);
}
A try without a catch is accepted and executes its body normally. There is no finally
syntax in the current parser.
Modules and imports
Import a module for its side effects, or import named exports. Named imports may be renamed with
exportName: localName.
import "./setup.os";
import { println } from "core:io";
import { isEven: checkEven } from "lib:number";
Built-in module imports require named imports. Relative module paths resolve from the importing file.
Library imports use lib:name and resolve under the executable's lib
directory; path traversal outside that directory is rejected. Circular imports are detected and
reported.
Top-level declarations are made available as module exports. Use the import form supported by the target module; built-ins validate requested export names.
Core modules
Core modules are imported by name and expose only named exports.
API reference
Choose an API from its module submenu to read its behavior and a runnable usage example.
Bundled libraries
Library modules are imported with the lib:name form and loaded from the executable's
lib directory. The shipped modules provide these primary interfaces:
| Module | Primary API | Behavior |
|---|---|---|
lib:argparse |
ArgumentParser(config?), ArgumentError(message) |
Creates a command-line parser. Configure it with prog,
description, allow_abbrev, and add_help;
register options and positionals with add_argument(definition). Parser
methods include parse_args(args?),
parse_known_args(args?), format_help(), and
print_help(). Argument definitions support defaults, choices, type
conversion, required options, and nargs forms ?,
*, +, or a fixed count. Parsing errors are returned as
objects with an error field; known-argument parsing returns a
namespace and unknown list.
|
lib:number |
isEven(value), isOdd(value),
isPositive(value), isNegative(value),
isZero(value), isInteger(value)
|
Numeric predicates for parity, sign, zero, and integral values. Zero is neither positive nor negative; integer checks reject values with a fractional part. |
lib:crypto |
CryptoJS-style hashes, HMAC, WordArray, encoders, algorithm factories, PBKDF2, and EvpKDF. | Bundled facade over the native cryptographic primitives; legacy hashes are documented with their interoperability limitations. |
lib:tui |
Terminal colors, status messages, prompts, confirmation, and table output. | ANSI-based terminal helpers and simple text-table rendering. |
Runtime features
- Built-ins:
core:io,core:file,core:math,core:number,core:unicode,core:sys,core:path,core:json,core:regex,core:error,core:http, andcore:socketandcore:cryptoprovide I/O, filesystem, numeric, Unicode, host-system, path, JSON, regular-expression, error, HTTP, loopback TCP, and cryptographic operations. - Built-in value methods: arrays provide indexed access, mutation, and
len/map/filter/find; objects providemap/filter/find; strings provide length, search, case-conversion, trimming, splitting, and replacement methods. - Promises: async calls return promises. Promise values expose
->then(fn)and->catch(fn)chaining. - Closures: nested functions can capture and mutate variables from enclosing functions.
- Errors: parser/compiler errors include source positions; runtime errors can be
handled with
try/catch. - Line mapping: generated bytecode retains source line information for runtime diagnostics.
Implementation notes
This page documents syntax and behavior visible in the parser/compiler implementation, not a complete formal language specification. A few boundaries are especially useful to know:
- There are no parser forms for
for,while,do,finally, or labels; looping usesloop. - Named
fnandclassdeclarations use top-levelname::fn/name::classsyntax. Anonymous function and class expressions are also supported. - Class declarations and expressions may omit the constructor; the runtime supplies an empty one.
- Switch statement matching is performed at dispatch before bodies run. Once a body begins,
C-style fall-through applies unless
breakor the case-specificcontinuechanges control flow. - Switch expressions require a
defaultand are parsed as comma-separated alternatives. - The compiler validates declaration placement, duplicate local names, constant assignment, await context, and break/continue context.
main/parser.go and main/compiler.go at the time this reference was
generated. Runtime built-in modules may expose additional APIs beyond syntax covered here.