novac
v2.4.0
Published
a rewrite version of my package nvlang
Readme
novac Language Reference
novac is a dynamic, expressive scripting language with a JavaScript-style syntax, a rich infix operator set, built-in event system, HTTP primitives, a Nova Classic layer, and a comprehensive standard library. Files use the .nv or .nova extension and are run with the novac CLI.
Table of Contents
- Comments
- Preprocessor Directives
- Literals & Values
- Variables & Smart Modifiers
- Types
- Operators
- Strings & F-Strings
- Control Flow
- Functions
- Classes
- Pattern Matching
- Events
- Error Handling
- Modules & Namespaces
- HTTP & Networking
- Nova Classic Features
- Built-in Global Objects
- Built-in Class Objects
- Standard Library (std)
- Standard Built-ins (bstd / import_builtin)
- Array Methods
- Object Methods
- String Methods
- Type System & Casting
- Threads
Comments
// single-line comment
/! also a single-line comment
/* multi-line block comment */
/?/ core.print("executed at lex time") // executable comment — runs during lexingPreprocessor Directives
#define NAME value // substitution macro (NAME replaced everywhere in source)
#define FLAG // valueless flag (for ifdef/ifndef)
#undef NAME // remove a macro
#ifdef FLAG // include following block only if FLAG is defined
#ifndef FLAG // include following block only if FLAG is NOT defined
#endif // close an ifdef/ifndef block
#register operator NAME {"precedence":N, "isUnary":bool}
#inject <json-token> // inject a raw token into the token streamLiterals & Values
Numbers
| Form | Example | Notes |
|------|---------|-------|
| Decimal | 42, 3.14 | Standard |
| Separator | 1_000_000 | Underscores ignored |
| Hex | 0xFF | Case insensitive |
| Binary | 0b1010 | Case insensitive |
| Custom base | 0r16FF | 0r<base><digits>, base 2–36 |
| Holland/scale | 0hbillion, 0hmega | Named SI / large-number literals |
| BigInt | 42n | Suffix n |
| Scientific | 1e6, 2.5E-3 | Standard scientific |
| Short suffix | 10k, 2m, 1b, 4t | k=1e3, m=1e6, b=1e9, t=1e12 |
Holland scale literals — 0h prefix:
| Name | Value | Name | Value |
|------|-------|------|-------|
| kilo | 1e3 | milli | 1e-3 |
| mega | 1e6 | micro | 1e-6 |
| giga | 1e9 | nano | 1e-9 |
| tera | 1e12 | pico | 1e-12 |
| million | 1e6 | femto | 1e-15 |
| billion | 1e9 | atto | 1e-18 |
| trillion | 1e12 | googol | 1e100 |
| quadrillion | 1e15 | | |
| quintillion | 1e18 | | |
Special Literals
| Token | Value |
|-------|-------|
| true | boolean true |
| false | boolean false |
| null | null |
| nstr | "" (empty string) |
| nfunc | A function that returns null |
URL Literals
Tokens beginning with a recognized scheme are URL literals:
let url = https://api.example.com/users
let local = localhost:3000
// Recognized: http, https, ftp, ws, wss, content, localhost:<port>Arrays
let arr = [1, 2, 3]
let spread = [...arr, 4, 5]Objects
let obj = { x: 1, 'y': 2 }
let method = { greet() { give "hi"; } }
let overload = {
[op]: {
unary: { '~': () => 17 }
binary: { '+': (a) => a*10 }
}
}
core.print(~obj + obj); // ~obj becomes 17, now 17 + obj becomes 17*10 which is 170.
let computed = { ["key" + idx]: value }
let withSpread = { ...obj, z: 3 }
let getset = {
get prop() { give this._val; },
set prop(v) { this._val = v; }
}
let presence = { active? true } // boolean-constrained property: must be true or falseVariables & Smart Modifiers
var x = 10 // mutable, function-scoped
let y = 20 // mutable, block-scoped
const Z = 30 // immutable binding/
// special __...__ identifiers:
let __my cool variable++-__ = 19; // when novac sees __, it collects everything until __ as the var name and gives an identifier token type, so you can even name a variable __26__ and the token with be { type: 'IDENTIFIER', value: '26' }Destructuring
let { a, b } = obj
let { a: renamed, b: other = "default" } = obj
let [first, second] = arr
let [head, ...tail] = arrSmart Variable Modifiers
Placed between the variable name and =:
| Modifier | Effect |
|----------|--------|
| frozen | Throws on any reassignment after first set |
| lazy | Expression evaluated only on first read |
| tracked | console.logs every assignment change |
| nonull | Throws if assigned null or undefined |
| once | Can only be set once; further assigns silently ignored |
| setter <fn> | Calls fn(newVal, oldVal) on every assignment; if fn returns non-undefined, that becomes the stored value |
| getter <fn> | Calls fn() on every read |
| as fnum [min, max] | Clamps to float range on every assign |
| as fint [min, max] | Clamps to integer range (Math.trunc) on every assign |
let x frozen = 5
let y lazy = computeHeavy()
let z tracked = 0
let n nonull = getValue()
let once_val once = initialize()
let watched setter mySetFn = 0
let computed getter myGetFn = 0
let clamped as fnum [0.0, 1.0] = 0.5
let bounded as fint [0, 255] = 128Links and $-notations
let obj = { test: { example: 29 } };
let link2example = link(obj.test.example); // link is not a function, its a special keyword function
link2example = 3; // example is also set
obj.test.example; // 3
obj.test.example = 2;
link2example; // 2
// you can also directly set a link call:
link(obj.test.example) = 1; // works
// nest links:
link(link2example) = 3; // now obj.test.example is also 3// $ notation:
// when used as a normal call:
$29; // noop, just 29
${a: 6 + 3}; // noop again, just the object
// real magic in $ assingments:
$"hi" = 29; // sets variable hi
$26 = 1; // set variable 26, even tho you can't just read from a var directly called 26, you can still use __26__ to read (novac trims __...__ and just leaves an ident with value ...)Type Annotation
let count: int = 0
let name: string = "Nova"
func add(a: int, b: int): int => { give a + b }Pointer Declaration & Dereference
var *ptr = someValue
let result = *ptr // dereference
*ptr = newValue // assign through pointerTypes
Type Alias
type ID = string
type Status = "active" | "inactive" | "pending"
type Point = { x: int, y: int }
type Result<T> = T | nullStruct
struct Vec2 {
x: int = 0,
y: int = 0
}
let v = Vec2({ x: 3, y: 4 })
v.x // 3Interface
interface Serializable {
serialize(): string
deserialize(data: string): bool
name?: string // optional member
}
interface Extended extends Serializable, Printable { }Enum
enum Status { Active, Inactive, Pending }
enum Shape {
Circle(int),
Rect(int, int),
Point = 0
}
let s = Status.Active
s.__variant__ // "Active"
s.__enum_type__ // "Status"Trait & Impl
trait Drawable {
draw(ctx) { }
}
impl Drawable of Circle {
draw(ctx) { ctx.circle(this.x, this.y, this.r); }
}
// Also: impl Drawable for Circle { }Type Expressions
string | null // union
int & Serializable // intersection
string[] // array type
{ name: string, age?: int } // inline shape type
Map<string, int> // genericOperators
Arithmetic
| Op | Meaning |
|----|---------|
| + - * / % | Standard arithmetic |
| ** | Exponentiation (right-associative) |
| ++ -- | Increment / decrement (prefix or postfix) |
Assignment
| Op | Meaning |
|----|---------|
| = | Assign |
| += -= *= /= %= **= | Compound assign |
| &&= | Logical-AND assign |
| \|\|= | Assign if current is null/undefined |
| ??= | Nullish-coalesce assign |
Comparison
| Op | Meaning |
|----|---------|
| == != | Loose equality |
| === !== | Strict equality |
| < > <= >= | Relational |
| is | Strict equality alias |
| isnt | Non-identity alias |
| equals | === alias |
| equals_ignore | Case-insensitive string equality |
| cmp | localeCompare — returns -1/0/1 |
Logical
| Op | Meaning |
|----|---------|
| && / and | Logical AND |
| \|\| / or | Logical OR |
| ! / not | Logical NOT (unary) |
| ?? | Nullish coalesce |
| xor | Exclusive OR |
| nand | NOT AND |
| nor | NOT OR |
| xnor | Exclusive NOR |
Bitwise
| Op | Meaning |
|----|---------|
| & \| ^ ~ | Bitwise AND / OR / XOR / NOT |
| << >> >>> | Left / right / unsigned right shift |
Type & Predicate
| Op | Meaning |
|----|---------|
| typeof | Returns type string (unary) |
| instanceof | Instance check |
| in | Membership / range containment |
| istypeof | typeOf(left) === right |
| matches | Regex test of left string against right |
| between | left > arr[0] && left < arr[1] |
Special / Flow
| Op | Meaning |
|----|---------|
| \|> | Pipe: x \|> f → f(x) |
| => | Arrow function body |
| .. | Inclusive range: 1..10 → NovaRange(1,10) |
| ... (unary) | Spread / rest |
| ... (binary) | Exclusive range: 1...10 → array [1..9] |
| ?. | Optional chain — null if left is null |
| ? | Ternary start |
| :: | Namespace / property access |
| >> | Pipe-right: val >> fn → fn(val); also used in compose / engage |
| delete | Delete variable, property, or subscript |
| void | Evaluate and return undefined |
| # | Size of right operand (array/object/string length) |
Array / Collection Infix
| Op | Meaning |
|----|---------|
| intersect | Elements in both arrays |
| union | Deduplicated merge |
| diff_arr | Elements in left not in right |
| zip | [[a0,b0],[a1,b1],...] |
| extend | Merge objects / concatenate arrays |
| concat | Concatenate |
| index | arr index 2 → element at 2 |
| step | 0..10 step 2 → [0,2,4,6,8,10] |
Numeric Infix
| Op | Meaning |
|----|---------|
| avg | (a + b) / 2 |
| diff | Math.abs(a - b) |
| ratio | a / b |
| mult_of | a % b === 0 |
| gcd | Greatest common divisor |
| lcm | Least common multiple |
| pow | Alias for ** |
| bigger / smaller | a > b / a < b aliases |
String Infix
| Op | Meaning |
|----|---------|
| pad_start | .padStart(right, ' ') |
| pad_end | .padEnd(right, ' ') |
Operator Precedence (highest → lowest)
| Prec | Operators |
|------|-----------|
| 15 | ++ -- (postfix), ?., ::, #:, $:, index |
| 14 | Unary: ! ~ - + * ... not typeof void delete |
| 13 | ** pow (right-assoc) |
| 12 | * / % ratio mult_of gcd lcm |
| 11 | + - avg diff pad_start pad_end concat |
| 10 | >> << >>> .. step zip |
| 9 | < > <= >= bigger smaller |
| 8 | == != === !== is isnt equals equals_ignore cmp istypeof matches instanceof in |
| 7 | & intersect diff_arr |
| 6 | ^ |
| 5 | \| union extend |
| 4 | && and nand |
| 3 | ?? xor xnor |
| 2 | \|\| or nor |
| 0 | = += -= *= /= %= **= &&= \|\|= ??= => \|> |
Strings & F-Strings
let a = "hello"
let b = 'world'
// Escape sequences: \n \t \r \\ \' \"...etc
// F-strings: f"..." with {} interpolation — any expression allowed
let msg = f"\color{blue}Hello {name}!\reset"
let math = f"2^10 = {2 ** 10}"
let complex = f"items: {arr.map(x => x * 2).join(', ')}"Control Flow
If / Else
if (cond) { } else if (other) { } else { }
let x = a > b ? a : b // ternary
let y = value if condition else alt // postfix if-expressionUnless / Until / Repeat
unless (cond) { } // runs when cond is false
until (cond) { } // runs while cond is false
repeat (n) { } // runs exactly n timesWhile / Do-While
while (cond) { }
do { } while (cond)For
for (let i = 0; i < 10; i++) { }
for (let item of array) { }
for (let key in object) { }Each
each item of array { }
each item, index of array { }Switch / Case
switch (value) {
case 1: core.print("one"); break;
default: core.print("other"); break;
}Match
match (value) {
when 1 { }
when 2, 3 { } // comma = multi-pattern
when 1..10 { } // range
when "ok" where cond { } // with guard
default { }
}Loop Control
| Keyword | Effect |
|---------|--------|
| break | Exit loop |
| continue | Next iteration |
| skip | Alias for continue (parsed as skip, no-op in execute) |
| goback | Throw { __return: scope.__return } — return from function |
| end | Throw { __return: undefined } — terminate scope |
Guard
guard (cond) {
// body runs only when cond is TRUE (passes through)
} else {
// runs when cond is FALSE
}When
when cond do { } // runs body once if cond is truthy
when cond then { } // sameWhere (scoped bindings)
where (base = 100, rate = 0.15) {
let tax = base * rate
}With
with (object) {
// object properties brought into scope
}
with option featureFlag {
// body runs with flag set; removed after block
}Loop (Classic iteration)
loop item in iterable { }Foreach (Classic key-value)
foreach(collection)(key, value) { }
foreach(collection)(key, value, length) { } // third var = total lengthWait (sync sleep)
wait(1000) // blocks for 1000ms (Atomics.wait, fallback: child process)Time Block
time {
// times this block; prints elapsed ms on completion
}Functions
Named Functions
func add(a, b) => {
give a + b
}
function greet(name) {
return f"Hello {name}"
}Function Modifiers
Modifiers come after func/function and before the function name. Multiple modifiers can be chained in any order.
| Modifier | Effect |
|----------|--------|
| async | Marks async; await blocks synchronously via SharedArrayBuffer |
| Strict | Throws if argument count doesn't match exactly |
| once | Body only runs once; later calls return the first result |
| memo | Results memoized by JSON.stringify(args) key |
| generator | Returns an iterator; yield inside collects values |
| timeout <expr> | Throws if function exceeds expr ms |
| defer <stmt> | Executes stmt when function returns (in finally) |
func async load(url) => { }
func Strict strict(a, b) => { }
func once init() => { }
func memo fib(n) => { }
func generator counter() => { }
func async memo cachedFetch(url) => { }
func timeout 5000 riskyOp() => { }
func defer cleanup() riskyOp() => { }
// function keyword works identically
function async load(url) { }
function Strict strict(a, b) { }in objects, you can have a [expr] quick access method that runs on obj.(...args) example:
let obj = { [expr]: (a, b) => return a + b }
core.print(obj.(1,5)); // 6Generator Iterator API
| Method | Description |
|--------|-------------|
| .next() | { value, done } |
| .last() | Step back one |
| .current() | Peek without advancing |
| .seek(n) | Jump to index n |
| .setIndex(n) | Set position |
| .currentIndex() | Current index |
| .length() | Total collected values |
| .at(n) | Value at index n |
| [Symbol.iterator] | Makes it iterable in for-of |
Return / Give / Goback
return value // sets __return, does NOT terminate
give value // alias — same as return, but terminates the function by throwing
goback // throws { __return: scope.__return }
yield value // collects into generator's output arrayArrow Functions
let double = x => x * 2
let add = (a, b) => a + bRest & Default Parameters
func sum(...nums) => { give nums.reduce((a, b) => a + b, 0) }
func greet(name = "World") => { }Block functions:
these auto call on reference, and use the returned value
block coolBlock { core.print(8); return 8; }
core.print(coolBlock); /* result:
8 //from inside coolBlock
8 //from core.print(coolBlock);
*/
// you can also just run the block using nova.runBlock('coolBlock')Run bounder:
// run CODE
// or
// run { ... }
// example:
core.print(run let x = 10); //10 because run uses last result if no return/give
core.print(x); // 10
core.print(run { let c = 8; return c; }); // 8
core.print(c); // 8
Pipe Operator
let result = 5 |> double |> addOneClasses
class Animal {
name: "unknown"
speak() { core.print(f"{this.name} speaks") }
}
class Dog extends Animal {
breed: "mixed"
speak() { core.print(f"{this.name} barks") }
}
class Doc implements Serializable {
// must satisfy all non-optional interface members
}Getter / Setter
class Box {
_val: 0
get value() { give this._val; }
set value(v) { this._val = v; }
}Decorators
@logged
@deprecated("use newFn")
class OldClass {
@readonly
name: "old"
}Pattern Matching
match (value) {
when 90..100 { } // range — uses NovaRange.includes()
when "ok" { } // scalar — uses ==
when 1, 2, 3 { } // any of these — comma-separated
when x where x > 0 { } // with guard expression
default { }
}Events
emit "eventName"
emit "eventName", payload
on "eventName" (data) {
core.print(data)
}
// Internal: fires on every statement execution step
on "nv:tick" () { }Error Handling
try {
riskyOp()
} catch (err) {
core.print(f"caught: {err}")
} finally {
cleanup()
}
try { riskyOp() } catch { } // no binding — catch without name
throw "plain string"
throw new Error("typed")
assert condition
assert condition, "failure message"
expt "expectedOutput" from {
core.print("expectedOutput")
}Modules & Namespaces
Import
import "path/to/module"
import "path/to/module" as alias
import "path/to/module" as name1, name2
from "module" import func1, func2
import_builtin Air, Chalk, Input, ShemaExport
export myValue
export { name = value, other = value2 }
default myMainExportNamespace
namespace Math {
func add(a, b) => { give a + b }
}
Math::add(1, 2)
using namespace Math // bring all members into scopeUsing / Unuse
using featureFlag
using namespace MyNS
unuse featureFlagEnvironment Variable
env VAR_NAME // reads process.env.VAR_NAME; throws if undefinedHTTP & Networking
First-Class HTTP Verbs
get https://api.example.com/users
post https://api.example.com/users({ name: "novac" })
put https://api.example.com/users/1({ name: "Updated" })
delete https://api.example.com/users/1()
patch https://api.example.com/users/1({ active: false })
head https://api.example.com/health
options https://api.example.com/
// Returns: { ok: bool, status: int, body: object|string, text: string }Sync fetch strategies (tried in order): in-process local Nova server dispatch → curl → PowerShell (Windows) → Node child process.
Fetch
fetch(url)
fetch(url, { method: "POST", headers: { Authorization: "Bearer x" }, body: payload })
fetch(url, opts) => resultVar // statement form — assigns resultServer Declaration
server(3000) {
get "/api/users" (req, res) {
res.json([])
}
post "/api/users" (req, res) {
res.json({ created: true, body: req.body })
}
delete "/api/users/:id" (req, res) {
res.json({ deleted: req.params.id })
}
}
// req: { method, url, path, headers, body, params, query }
// res: .status(code) .header(k,v) .json(data) .send(data)
// :param values also bound directly into route scope
// If res.json/send never called, route handler's return value is auto-sent@classic — Classic Compatibility Block
Classic novac keywords are only valid inside an @classic { } block. Outside of one they are plain identifiers and cause a parse error if used as statements. This keeps the core language surface area clean while preserving full backward compatibility for legacy code.
@classic {
// any classic keyword works in here
echo "hello"
keep port = 8080
gear(500) poll {
let data = fetch(https://api.example.com/data)
echo data.body
}
engage poll
}If you use a Classic keyword outside @classic, the parser throws:
ClassicError: 'echo' is a Classic novac keyword and must be used inside an @classic { } block.
Wrap your legacy code: @classic { echo ... }@classic blocks can appear anywhere a statement is valid — top-level, inside functions, inside if bodies, etc. They share the same scope as their surroundings.
func migrate() => {
@classic {
backup session = currentSession
temp session = "migration" => {
runMigration()
}
retrieve session
}
}Classic Keywords (only valid inside @classic { })
Output:
| Keyword | Behavior |
|---------|----------|
| echo value | process.stdout.write(String(value)) — no newline |
| print(val, ...) | Space-joined args + newline |
| println(value) | process.stdout.write(value) — no newline |
| log(val, ...) | console.log(...args) |
| logln(value) | process.stdout.write(value) |
| warn "msg" | "Warning: msg\n" |
| info "msg" | "INFO: msg\n" |
| hello | Prints "Hello, !" |
| hello "a", "b" | Prints "Hello, a, and b!" |
| banner "text" | Box-drawn ASCII banner |
Scoped Binding:
| Keyword | Behavior |
|---------|----------|
| temp x = val => { } | Override x for the block duration, then restore |
| keep x = val | Set x only if currently null/undefined; silent otherwise |
Macros & Named Code:
| Keyword | Behavior |
|---------|----------|
| macro NAME = val | Store value in nova.macros and scope |
| snippet name { } | Register named snippet; run with nova.runSnippet("name") |
| defunc name(args) => { } | Declare function (arrow-body style) |
| lambda name = x => expr | Declare arrow function |
| compose name = f >> g | Compose functions left-to-right: g(f(x)) |
| partial name = fn(arg) | Partial application |
Iteration:
| Keyword | Behavior |
|---------|----------|
| loop item in iterable { } | For-of style |
| foreach(col)(key, val) { } | Key-value iteration |
| foreach(col)(key, val, len) { } | With length variable |
State:
| Keyword | Behavior |
|---------|----------|
| backup x = x | Save value to executor backup store |
| retrieve x | Restore from backup store |
| addto arr val | Push value to array |
| addto map val : key | Set map[key] = val |
Async Tasks:
| Keyword | Behavior |
|---------|----------|
| gear(ms) name { } | Named loop task; waits ms between iterations |
| gear name { } | Named loop task; no wait |
| engage a >> b | Run gears left-to-right in continuous loop until break |
Isolation:
| Keyword | Behavior |
|---------|----------|
| sandbox { } | Run in isolated Node.js vm context; scope vars snapshotted in/out |
| infer ("model") => var: "prompt" | Run local AI model via ollama |
Data:
| Keyword | Behavior |
|---------|----------|
| sstream name => { put val } | Named sync stream with put accumulator |
| lend fn src to dst | Merge src function body into dst |
| lend m map with fn | Attach fn as method on map object |
Session / Resumable:
| Keyword | Behavior |
|---------|----------|
| session("name") { } | Store named session body |
| enter key type | Log [enter] key type — interactive entry point marker |
| resu name(args) => { }, | Resumable: re-parsed and run fresh on each call |
| keyfunc name(x) { } { } | Pattern-matched function dispatch |
Annotation / Registry:
| Keyword | Behavior |
|---------|----------|
| describe "text" | Append to nova.descriptions |
| using flagName | Set this.options[flag] = true |
| unuse flagName | Delete this.options[flag] |
| using namespace MyNS | Bring all namespace members into scope |
Type Ops:
| Keyword | Behavior |
|---------|----------|
| classify value | Print/return type string |
| classify value as Type | Return bool |
| rate(v) type | Type-cast (see full table in Type System section) |
Control:
| Keyword | Behavior |
|---------|----------|
| skip | No-op (parsed, returns undefined) |
| end | Terminate current scope: throws { __return: undefined } |
| clear | Reset nova.descriptions to [] |
| time { } | Time a block; print elapsed ms |
| wait(ms) | Sync sleep via Atomics.wait |
Misc:
| Keyword | Behavior |
|---------|----------|
| expt "val" from { } | Capture stdout; error if actual.trim() !== "val" |
| option name = fn | Set runtime option/hook |
| env VAR | Read process.env.VAR into scope; throws if undefined |
| eval expr | Re-evaluate an already-parsed AST node |
| declare (expr) as x | Assign to x |
| declare (expr) into x | Append string to x |
| declare (expr) as x log | Assign and print |
| declare (expr) as x throw | Assign and throw |
| declare (expr) as x finalize | Assign and return |
Built-in Global Objects
Always in scope — no import needed.
core
| Member | Description |
|--------|-------------|
| core.print(v) | process.stdout.write(stringify(v) + '\n') |
| core.json | Reference to JSON |
| core.getAst() | Returns current program AST |
| core.vars | Reference to global scope variables |
| core.types | Reference to the TypeRegistry |
| core.register(fn) | Wraps a novac function as a native callable |
| core.import | Module import function |
| core.ndb() | Dev bridge: { exe, scopes, types, eventBus, gears, backups, macros, blocks, snippets, keyfuncs, sessions, resus, namespaces } |
nova (Unified Registry)
Live proxy over all internal registries:
| Member | Description |
|--------|-------------|
| nova.version | "2.0" |
| nova.macros | All defined macros |
| nova.blocks | All defined blocks |
| nova.snippets | All defined snippets |
| nova.gears | All defined gears |
| nova.sessions | All defined sessions |
| nova.resus | All defined resus |
| nova.keyfuncs | All defined keyfuncs |
| nova.backups | All backed-up values |
| nova.options | Current option flags |
| nova.descriptions | Array of all describe annotations |
| nova.scope | Reference to global scope |
| nova.types.structs | Registered struct names |
| nova.types.interfaces | Registered interface names |
| nova.types.enums | Registered enum names |
| nova.types.traits | Registered trait names |
| nova.types.check(val, typeName) | Runtime type check → bool |
| nova.types.list() | All registered type names |
| nova.events.events() | Active event names |
| nova.events.count(ev) | Number of listeners |
| nova.events.has(ev) | Has listeners? |
| nova.events.clear(ev) | Remove all listeners for event |
| nova.events.clearAll() | Remove all events |
| nova.setMacro(name, val) | Define/update a macro |
| nova.getMacro(name) | Get macro value |
| nova.hasMacro(name) | Check if macro exists |
| nova.setBlock(name, fn) | Register a native block |
| nova.runBlock(name) | Execute a named block |
| nova.runSnippet(name, ...args) | Execute a named snippet |
| nova.emit(event, val) | Emit event |
| nova.on(event, fn) | Listen for event |
| nova.eval(code) | Parse and run a novac code string |
| nova.inspect(name) | { inScope, isMacro, isBlock, isSnippet, isGear, isSession, isResu, value } |
| nova.clearAll() | Clear macros, blocks, snippets, gears, sessions, descriptions |
nvk (Platform / System Namespace)
nvk exposes all system and I/O operations. These replaced the previous statement-based system keywords.
| Member | Description |
|--------|-------------|
| nvk.notify(title, content) | Termux notification (fallback: stdout) |
| nvk.toast(msg) | Termux toast |
| nvk.vibrate(ms) | Termux vibrate |
| nvk.clipboard(text) | Set clipboard via Termux |
| nvk.camera(path) | Take photo via Termux |
| nvk.share(path) | Share file via Termux |
| nvk.open(path) | Open file via Termux |
| nvk.cpu | Array of CPU model strings |
| nvk.mem | Total memory e.g. "8.00GB" |
| nvk.hostname | os.hostname() |
| nvk.uptime | os.uptime() in seconds |
| nvk.pid | process.pid |
| nvk.arch | os.arch() |
| nvk.platform | process.platform |
| nvk.osPlatform | os.platform() |
| nvk.userInfo | os.userInfo() as NovaObject |
| nvk.network | os.networkInterfaces() as NovaObject |
| nvk.load | os.loadavg() as NovaArray |
| nvk.tmpDir | os.tmpdir() |
| nvk.cwd | process.cwd() |
| nvk.pathDir(p) | path.dirname(p) |
| nvk.pathBase(p) | path.basename(p) |
| nvk.pathExt(p) | path.extname(p) |
| nvk.pathJoin(...parts) | path.join(...parts) |
| nvk.sha256(s) | SHA-256 hex digest |
| nvk.randomBytes(n) | N random bytes as hex string |
| nvk.uuid() | Random UUID v4 |
| nvk.parseURL(raw) | { hostname, pathname, search, protocol, port } |
| nvk.readFile(path) | fs.readFileSync(path, 'utf8') |
| nvk.writeFile(path, content) | fs.writeFileSync |
| nvk.createFile(path, content) | Create/overwrite file |
| nvk.deleteFile(path) | fs.unlinkSync |
| nvk.listFiles(dir) | fs.readdirSync(dir) as NovaArray |
| nvk.exists(path) | fs.existsSync(path) |
| nvk.execFile(path) | Parse and run a .nova file |
| nvk.sh(cmd) | execSync(cmd) — prints and returns stdout |
| nvk.exec(code) | Parse and run a novac code string |
| nvk.term(cmd, shell) | Run cmd in specified shell (bash default) |
| nvk.banner(text) | Print box-drawn banner |
| nvk.warn(msg) | "Warning: msg" |
| nvk.info(msg) | "INFO: msg" |
qae (Query Assertion Engine)
All members are functions (a) => bool or (a) => value:
| Member | Description |
|--------|-------------|
| qae.even(a) / qae.odd(a) | Parity check |
| qae.integer(a) | Number.isInteger(a) |
| qae.positive(a) / qae.negative(a) / qae.zero(a) | Sign check |
| qae.finite_(a) / qae.infinite(a) / qae.nan_(a) | Numeric state |
| qae.isnull(a) / qae.defined(a) | Nullability |
| qae.trimable(a) / qae.trimmed(a) | Whitespace |
| qae.uppercase(a) / qae.lowercase(a) | Case transform |
| qae.numeric(a) / qae.alpha(a) / qae.alnum(a) / qae.blank(a) | String class |
| qae.palindrome(a) | Is palindrome (case-insensitive, strips spaces) |
| qae.empty(a) / qae.nonempty(a) | Emptiness |
| qae.unique(a) | Array has no duplicates |
| qae.first(a) / qae.last(a) / qae.length(a) | Array/string access |
| qae.truthy(a) / qae.falsy(a) | Boolean coercion |
| qae.type_(a) | typeof a |
| qae.string_(a) / qae.number_(a) / qae.boolean_(a) / qae.object_(a) / qae.array_(a) / qae.function_(a) | Type checks |
| qae.prime(a) / qae.composite(a) / qae.oddprime(a) | Number theory |
| qae.vowel(a) / qae.consonant(a) | Character class |
| qae.sorted(a) / qae.ascending(a) / qae.descending(a) | Order checks |
| qae.contains(a, b) / qae.startsWith_(a, b) / qae.endsWith_(a, b) / qae.matches_(a, b) | Containment |
| qae.divisible(a, b) / qae.between_(a, lo, hi) | Numeric predicates |
| qae.check(a, pred) | Apply function or named qae predicate to a |
novaRegex(pattern, flags)
Semantic regex with <name> placeholders. Returns a JS RegExp.
let emailRe = novaRegex("<email>")
emailRe.test("[email protected]") // true
novaRegex("<email|url>") // combined with |Semantic names: keyword symbol digit nondigit whitespace tab newline start end any wordboundary word variable integer float hex binary space title email url uuid date time color
Global Functions
| Function | Description |
|----------|-------------|
| typeOf(v) | "number" "string" "bool" "null" "array" "object" "range" "pointer" "function" "struct:Name" "enum:Name" |
| typecheck(v, typeName) | Runtime type check → bool |
| satisfies(v, ifaceName) | Does value satisfy all required interface members? |
| fetch(url, options?) | Sync HTTP → { ok, status, body, text } |
| setTimeout(fn, delay) | Schedule function after delay |
Built-in Class Objects
All available globally:
ForLoop(opts)
ForLoop({ from: 1, to: 10 }).map(i => i * i).collect()
ForLoop({ from: 0, to: 20, step: 2 }).toArray()
ForLoop({ from: 1, to: 100 }).do(i => { sum += i }).run()| Method | Description |
|--------|-------------|
| .from(n) .to(n) .step(n) | Configure range |
| .do(fn) / .each(fn) / .map(fn) | Set body |
| .run() | Execute without collecting |
| .collect() | Execute and collect results |
| .toArray() | Collect index values |
| .filter(fn) | Convert to Pipeline then filter |
| .pipe() | Convert to Pipeline |
| .toStream() | Convert to DataStream |
WhileLoop(opts)
WhileLoop({}).cond(() => n < 32).do(() => { n *= 2 }).maxIter(100).run()IfBlock(opts)
IfBlock({ cond: score >= 90, then: () => "A" })
.elseIf(score >= 80, () => "B").else(() => "F").run()MatchBlock(subject)
MatchBlock(404)
.when(200, () => "OK")
.when(404, () => "Not Found")
.when(90..100, x => "A")
.when(x => x > 500, x => "server err")
.default(() => "Unknown")
.run()TryCatch()
TryCatch().try(() => { throw "oops" }).catch(e => core.print(e)).finally(() => {}).run()Pipeline(initial)
Pipeline([1,2,3,4,5]).filter(x => x % 2 == 0).map(x => x*x).take(2).collect()| Method | Description |
|--------|-------------|
| .pipe(fn) / .map(fn) | Transform |
| .filter(fn) | Filter |
| .tap(fn) | Side-effect passthrough |
| .flatMap(fn) | Map + flatten |
| .reduce(fn, init) | Immediate reduce |
| .take(n) / .skip(n) | Slice |
| .run() / .collect() | Execute |
FuncDef(opts)
FuncDef({ args: ["a", "b"], body: (a, b) => a + b }).named("add").call(10, 32)Timer()
let t = Timer(); t.start(); t.lap(); let ms = t.stop()
t.elapsed // ms since start
t.laps // NovaArray of lap timesCounter(initial, step)
let c = Counter(0); c.increment(5); c.decrement(2); c.clamp(0, 100); c.valueStack() / Queue() / LinkedList()
let s = Stack(); s.push(10); s.pop(); s.peek(); s.size; s.empty
let q = Queue(); q.enqueue("a"); q.dequeue(); q.peek(); q.size
let ll = LinkedList(); ll.push("a"); ll.shift(); ll.pop(); ll.unshift("z"); ll.sizeState(initial)
let sm = State("idle")
sm.add("idle", "start", "running").add("running", "done", "idle")
sm.onEnter("running", s => {}).onExit("idle", s => {})
sm.dispatch("start") // true = success
sm.current; sm.history; sm.is("running")Observable(initial) / Signal(initial)
let obs = Observable(0)
obs.subscribe((newVal, oldVal) => {})
obs.value = 42
let derived = obs.pipe(x => x * 2)
let sig = Signal(1)
let doubled = sig.derive(x => x * 2)
sig.value = 5 // doubled.value = 10 (automatic propagation)
sig.effect(v => {}) // runs immediately and on every changeValidator()
Validator().required().type("number").min(0).max(100).validate(50)
// { valid: true, errors: [], value: 50 }| Method | Description |
|--------|-------------|
| .required() .type(t) .min(n) .max(n) | Core validators |
| .minLen(n) .maxLen(n) .pattern(re) .email() | String validators |
| .custom(fn, msg) | Custom validator |
| .validate(v) | Returns { valid, errors: NovaArray, value } |
DataStream(source)
DataStream([5,3,8,1,9]).filter(x => x > 3).sort().reverse().take(3).collect()| Method | Description |
|--------|-------------|
| .map .filter .take .skip .flatMap | Transform |
| .distinct() | Deduplicate via Set |
| .sort(fn?) .reverse() .zip(other) | Reorder |
| .collect() .reduce(fn, init) .forEach(fn) | Materialize |
| .first() .last() .count() | Inspect |
Transformer() / TransformerJSON() / TransformerBase64()
let jt = TransformerJSON(); jt.to({ x: 1 }); jt.from('{"x":1}')
let b64 = TransformerBase64(); b64.to("Hello!"); b64.from(encoded)
// Static presets: Transformer.upper(), Transformer.trim_(), Transformer.number_()Router()
let router = Router()
router.on("/api/users/(.*)", id => f"user {id}").default(p => f"404: {p}")
router.dispatch("/api/users/42") // "user 42"EventBus()
let bus = EventBus()
bus.on("data", v => {}).once("ready", () => {}).off("data", handler)
bus.emit("data", 42); bus.events()Memo(fn, keyFn?) / Lazy(fn)
let m = Memo(x => x * x * x)
m.call(5); m.stats; m.clear(); m.invalidate(5)
let lazy = Lazy(() => expensiveOp())
lazy.value; lazy.reset(); lazy.map(fn)Standard Library (std)
Always available as std.X.
std.Math
abs ceil floor round sqrt pow max min log log2 log10 sin cos tan PI E trunc sign random plus:
| Member | Description |
|--------|-------------|
| clamp(v, lo, hi) | Math.min(Math.max(v,lo),hi) |
| floorTo(a, b) | Floor to nearest multiple of b |
| ceilTo(a, b) | Ceil to nearest multiple of b |
| factorial(n) | Memoized factorial |
| fibonacci(n) | Memoized fibonacci |
| divmod(a, b) | [quotient, remainder] |
std.Array
from(it) of(...items) range(start, end, step) isArray(v) fill(n, val) zip(...arrs)
std.Object
keys(o) values(o) entries(o) assign(target, ...srcs) freeze(o) has(o, k) create(proto)
std.String
from(v) padStart padEnd repeat includes startsWith endsWith trim split replace replaceAll toUpper toLower charAt charCodeAt fromCharCode slice indexOf
std.is
std.is.number(v) std.is.string(v) std.is.bool(v) std.is.array(v)
std.is.object(v) std.is.null(v) std.is.func(v) std.is.range(v)
std.is.integer(v) std.is.finite(v) std.is.NaN(v)std.fn
identity compose pipe memoize once partial curry noop always flip not
Other std Members
| Member | Description |
|--------|-------------|
| std.num(v) / std.str(v) / std.bool(v) / std.int(v) | Coercions |
| std.print(...) | console.log(...) |
| std.error(msg) | Throw an Error |
| std.range(start, end, step) | Create NovaRange |
| std.fnum(min, max, initial) | Clamped float with .value get/set |
| std.fint(min, max, initial) | Clamped integer with .value get/set |
| std.convertCase(str, target) | "snake" "camel" "pascal" "kebab" "upper" "lower" |
| std.randomWord() | Random pronounceable word |
| std.randomName(format?, minLen?, maxLen?) | Capitalized random name |
| std.console.* | log warn error info time timeEnd |
| std.Date.now() / std.Date.create(...) | Date utilities |
| std.json.parse(s) / std.json.stringify(v, sp?) | JSON with NovaObject unwrapping |
| std.Promise.* | resolve reject all race allSettled any |
Standard Built-ins (bstd / import_builtin)
Air
Object of references to all novac core modules keyed by filename.
Chalk
ANSI terminal colors (same API as chalk npm package):
import_builtin Chalk
core.print(Chalk.red("error"))
core.print(Chalk.bold.green("success"))History
File-backed input history stored as .184.<name>:
getKey(name) exists(key) createHistory(key) appendHistory(key, item, mode?, index?) readHistory(key) rawSetHistory(key, array) rawGetHistory(key) clearHistory(key)
Input
Synchronous terminal input with full line-editing:
import_builtin Input
let r = Input.prompt("Name: ")
// r: { result: "typed text", history: [] }
Input.prompt("Pwd: ", { password: true, passwordHash: "*" })
Input.prompt("Cmd: ", true) // enable history
Input.prompt("Search: ", { completer: buf => "suggestion" })Features: cursor, history ↑↓, backspace, delete, tab completion, Ctrl+C, password masking.
Shema / ShemaType
import_builtin Shema, ShemaType
let schema = new Shema({ name: ShemaTypes.String, age: ShemaTypes.Int })
schema.validate({ name: "novac", age: 2 })
schema.defaultObj()
new ShemaType("PositiveInt", val => Number.isInteger(val) && val > 0)Built-in ShemaTypes: Int Float String Boolean Array Object Function Null Undefined Symbol BigInt Date RegExp Shema Any
ConfigFile
import_builtin ConfigFile
let cfg = new ConfigFile("app", "json", false, mySchema)
cfg.writeFull({ host: "localhost", port: 8080 })
cfg.write(["database", "host"], "db.example.com")
cfg.readFull(); cfg.read(["database", "host"])
cfg.interactiveSetup(defaultConfig)JsDB
JsDB.class(JsClass) JsDB.run(code) JsDB.require JsDB.gt JsDB.fetch(url, opts) JsDB.process JsDB.version JsDB.platform JsDB.env
Storage
import_builtin Storage
Storage.setItem("key", { data: 42 })
Storage.getItem("key")
Storage.removeItem("key")
Storage.clear()Crypto
import_builtin Crypto
Crypto.hash("data") // SHA-256 hex
Crypto.hash("data", "sha512") // any Node.js crypto algoPathUtils
join resolve basename dirname extname
Base64
Base64.encode(s) Base64.decode(s)
OsUtils
platform homedir() tmpdir() cpus() totalmem() freemem() uptime() openFile(path) (Windows: requestAdminPrivileges())
Zip
import_builtin Zip
await Zip.zip(["a.txt"], "out.zip")
await Zip.unzip("out.zip", "./output/")Array Methods
Auto-dispatched on any NovaArray:
map filter reduce find findIndex some every flat flatMap sort reverse slice splice indexOf includes join push pop shift unshift concat fill forEach keys values entries at first last random unique count groupBy shuffle min max sum avg product toArray toObject toString | .length (property)
Object Methods
Auto-dispatched on any NovaObject:
keys() values() entries() has(k) assign(...srcs) | .length (property)
String Methods
Auto-dispatched on any string:
toUpperCase toLowerCase trim split includes startsWith endsWith slice indexOf lastIndexOf repeat replace match | .length (property)
Type System & Casting
as Cast
let n = value as int // Math.trunc(Number(v))
let f = value as float // Number(v)
let s = value as string // String(v)
let b = value as bool // Boolean(v)
let a = value as array // _toIterable(v) → NovaArray
let st = value as MyStructrate() Cast — Full Type Table
| Type | Operation |
|------|-----------|
| int | Math.trunc(Number(v)) |
| float / f64 | Number(v) |
| f32 | Math.fround(Number(v)) |
| string | String(v) |
| bool | Boolean(v) |
| bigint | BigInt(Math.trunc(Number(v))) |
| u8 | parseInt(v) & 0xFF |
| u16 | parseInt(v) & 0xFFFF |
| u32 | parseInt(v) >>> 0 |
| i8 | (parseInt(v) << 24) >> 24 |
| i16 | (parseInt(v) << 16) >> 16 |
| i32 | parseInt(v) \| 0 |
| char | String(v)[0] |
| array | _toIterable(v) → NovaArray |
| StructName | createStruct("StructName", v) |
Type Check
typecheck(42, "number") // true
satisfies(doc, "Serializable") // true if all required interface members present
classify value // returns type string
classify value as TypeName // returns boolCLI — novac
novac <file> [args...] Run a .nova / .nv file
novac test <file> Check syntax (exit 0 = OK, prints "Syntax OK")
novac format [file] Format a file (or stdin if no file given)
novac eval <code> Evaluate a novac code string inline
novac tokens <file> Print token stream as JSON
novac ast <file> Print AST as JSON
novac repl Start interactive REPL
novac new <project> Scaffold a new project directory
novac new-kit <dirname> Scaffold a new kit in current directory
novac init PATH Add npm bin to system PATH (writes to shell rc)
novac init build Bundle project into a .novamod file
novac config get [key] Read global/project config (all keys if no key given)
novac config set <key> <value> Write a config key
novac config init Interactive config setup wizard
novac etc notices Show runtime notices from stdlib
novac etc describe <file> Print a human-readable description of a .nova file's AST
novac etc kit <name> Install a built-in kit into ./nova_modules/<name>
novac etc kit <name> --global Install a built-in kit into ~/.novac/nova_modules/<name>
novac module install [path] Install a .novamod bundle (or all deps from nova.config.json)
novac module install [path] -g Install globally into ~/.novac/nova_modules
novac module list List all installed modules in nova_modules/
novac module remove <name> Remove an installed moduleRunning a File
novac src/main.nova
novac src/main.nova arg1 arg2
novac src/main.nova --port 8080 --debug
novac src/main.nova @addr1 +feature1All arguments after the file are parsed and exposed as the cli object inside the script (see below).
Argument Parsing Rules
| Argument form | Goes into |
|---------------|-----------|
| positional (no prefix) | cli.args array |
| -flag | cli.options.flag = true |
| --flag | cli.options.flag = true |
| --flag value | cli.options.flag = "value" |
| --flag=value | cli.options.flag = "value" |
| @name | cli.addrs.name = true |
| +feature | cli.additions.feature = true |
Arguments that start with -, @, or + are excluded from cli.args.
REPL Commands
Inside novac repl:
| Command | Description |
|---------|-------------|
| .exit | Exit the REPL |
| .clear | Clear the screen |
| .ast <code> | Print AST of inline code |
| .tokens <code> | Print token stream of inline code |
| .help | Show REPL help |
History is persisted to ~/.nova_repl_history.
novac new <project>
Scaffolds a project directory with:
<project>/
src/main.nova // starter file with print("Hello, Nova!")
bin/<project>.nv // entry script (empty, chmod 755)
nova_modules/ // local module directory
nova.config.json // project manifest
README.md
.gitignorenova.config.json fields: name, version, description, author, license, main, srcDir, scripts.run, dependencies, devDependencies.
novac init build
Reads nova.config.json from cwd, collects all .nova/.nv files from srcDir, and bundles them into <name>.novamod — a JSON file with:
{
"manifest": { "name", "version", "main", ...config },
"sources": { "relative/path.nova": "source code", ... },
"buildTime": "ISO timestamp"
}Install in another project: novac module install ./<name>.novamod
novac etc kit <name>
Installs a built-in kit from novac/kits/<name>/ into nova_modules/. Available built-in kits: kitnovacweb, kitlibfs, kitlibproc.
Built-in cli Object
When a file is run via novac <file> [args...], a cli object is automatically injected into the global scope:
// novac myapp.nova hello world --port 8080 --debug @prod +cache
cli.args // ["hello", "world"] — positional args (no flag prefix)
cli.options // { port: "8080", debug: true }
cli.addrs // { prod: true }
cli.additions // { cache: true }
cli.raw // process.argv — full raw argument arrayFull cli Object Shape
| Property | Type | Description |
|----------|------|-------------|
| cli.args | Array | Positional arguments (no -, @, + prefix) |
| cli.options | Object | All -flag, --flag, --flag=val, --flag val arguments |
| cli.addrs | Object | All @name arguments, values are true |
| cli.additions | Object | All +name arguments, values are true |
| cli.raw | Array | process.argv — complete unprocessed argument list |
Usage Examples
// novac server.nova --port 3000 --host localhost
let port = cli.options.port or "8080"
let host = cli.options.host or "0.0.0.0"
server(port) {
get "/" (req, res) { res.json({ host, port }) }
}// novac build.nova src/main.nova --minify --output dist/app.js
let inputFile = cli.args[0]
let minify = cli.options.minify or false
let output = cli.options.output or "out.js"// novac deploy.nova @production +cache
if (cli.addrs.production) {
core.print("Deploying to production")
}
if (cli.additions.cache) {
core.print("Cache warming enabled")
}Threads
func worker() => {
give heavyComputation()
}
let t = Thread(worker)
t.start()
t.send("message")
t.on_message(msg => { core.print(msg) })
let result = t.join()| Method / Property | Description |
|-------------------|-------------|
| .start() | Spawn the worker thread |
| .join() | Block until done; applies scope mutations; returns result |
| .send(value) | Post message to worker |
| .on_message(fn) | Handler for messages from worker |
| .result | Final return value |
| .error | Error string if worker threw |
| .done | Boolean: completed |
Scope snapshot: user-defined variables serialized and sent. Functions serialized via toString(). Worker can write back via thread.set(name, val) — applied atomically on .join().
Read also: NVML readme (./kits/kitnovacweb/README.md)
