Language Features

Marina is a strictly-typed subset of TypeScript, compiled ahead of time to LLVM IR. Programs use the .tsm extension and file names must be kebab-case.

Types & inference

TypeExample
Int32let a = 10
Float64let b = 20.5
stringlet s = "hi"
boollet ok = true
Promise<T>return value of an async function
Array<T>let xs = [1, 2, 3]
interfacestructural data contracts
generic Ttype parameters on functions and classes

Annotations are available (let a: Int32 = 10) and required for function parameters. There is no any. The only implicit conversion is Int32 → Float64 in mixed arithmetic. String arithmetic is rejected — use template literals:

let a = 10        // Int32
let b = 20.5      // Float64
let result = a + b  // Float64 (a is widened)
console.log(`The result of ${a} + ${b} is ${result}`)

Variables: let vs const

let declares a mutable variable; const declares an immutable one. Assigning to a const in any form (=, ++, --, +=, -=) is a compile-time error, and const declarations must be initialized. const freezes the binding, not the object — array contents can still be mutated through it.

Top-level variables are globals: they live for the whole program and are visible inside every function and lambda. Lambdas capture them by reference to the global — reads see the current value, and writes update the shared global.

Arrays

Typed lists with bounds-checked indexing (out-of-bounds panics at runtime) and reference counting. Element types are inferred, or taken from an annotation on an empty literal.

let xs = [1, 2, 3, 4, 5]          // Array<Int32>
xs.push(6); xs.pop(); xs.unshift(0); xs.splice(2, 1)
console.log(xs.length)

xs[0] = 10                          // index assignment
let strs: Array<string> = []
strs.push("hello"); strs.push("world")
console.log(strs.join("-"))          // hello-world

xs.forEach((item) => { console.log(item) })
let doubled = xs.map((item) => { return item * 2 })
let big = xs.filter((item) => { return item > 3 })
let sum = xs.reduce((acc, item) => { return acc + item }, 0)

Supported: push, pop, shift, unshift, splice, slice, concat, join, reverse, sort, length, plus the higher-order forEach / map / filter / reduce. Nested arrays work too.

Objects & classes

Objects are struct-of-fields values, ARC-managed. Two ways to get one:

Ghost classes — an object literal infers its shape at compile time. The checker mints an anonymous class from the literal; field types come from the values. No class definition needed, and the shape is still fully static:

let abc = {
    a: 5,          // Int32
    b: 6,          // Int32
    c: "Test"      // string
}
console.log(abc.a)   // 5
abc.b = 10           // field write

Accessing abc.d is a compile-time error. Declared classes define a named shape, instantiated with new. Fields are public by default; classes are pure data (no methods):

class Point {
    public x: Float64
    public y: Float64
    label: string
}
let origin = new Point()
origin.x = 0.0

With a class annotation on a let, an object literal must match the class field-for-field.

Generics

Functions and classes take type parameters, monomorphized at compile time — every distinct instantiation generates its own specialized copy, so generics cost nothing at runtime.

function pick<T>(a: T, b: T, takeFirst: bool): T {
    if takeFirst { return a }
    return b
}

class Box<T> { value: T, label: string }

let n = pick(10, 20, true)              // inferred: Int32
let box = new Box<Int32>()            // explicit type argument

Interfaces

interface declares a structural data contract: any object — a ghost class (object literal) or a declared class — that has the interface's fields with matching types conforms to it, and extra fields are allowed. Conformance is checked at compile time; interfaces are erased at runtime (field access is resolved by name through the runtime, so the same code works on any conforming shape).

interface Point { x: Int32, y: Int32, name: string }
interface Named { name: string }

function describe(pt: Point): string {
    return `${pt.name}: ${pt.x}, ${pt.y}`
}

let p = { x: 1, y: 2, name: "origin" }   // object literal conforms
let shapes: Array<Named> = [p, q, car]    // different concrete shapes

Async / await

JS-style promises. Each async function compiles to an entry function plus a resume state machine — one basic block per continuation segment. await registers the frame with the runtime; when the promise resolves, the runtime resumes it on a worker thread. The program exits when the event loop reaches quiescence.

async function fetchData() {
    return new Promise((resolve) => {
        setTimeout(() => { resolve("Data fetched") }, 1000)
    })
}

async function main() {
    let data = await fetchData()
    console.log(data)
}

Control flow

if/else, while, C-style for, with ++, --, +=, -=. Loop conditions must be bool (no truthiness); the for init variable is scoped to the loop. Entry point: define main (sync or async).

Standard libraries

Bring them in with a top-level use statement (use http — the JS-style import is not supported).

ModuleWhat it provides
netraw TCP: net.connect(host, port) → Stream with write / read_all / close
httpclient (http.get/http.post), low-level http.listen server, and an express-style API: new http.Server, server.router, router.get/post, http.JsonValidator with chainable field validators
fsreadFile, writeFile, exists, readDir, mkdir, remove
cryptopure-C++ sha256, base64Encode/Decode, randomHex, uuid
sqlSQLite via prepared statements: new sql.Connection, sql.stmt`...` tagged templates, db.execute / db.query — values are always bound as parameters, never string-concatenated

How a program compiles

Lex & parse → strict type check → LLVM IR codegen (with ARC retain/release insertion and async state machines) → clang links libmarina_runtime.a into a native binary. The runtime exposes a stable C ABI (marina_retain, marina_release, marina_promise_create, marina_await, …) that the generated IR calls directly.