Language Guide
DeltaScript is a typed superset that compiles to JavaScript (ESM). This guide highlights the core syntax and typing model.
Files and Modules
- File extension:
.ds - Emitted JavaScript:
.js(ESM) when building;.mjstemp when running a single file. - Imports/exports follow JavaScript semantics (compiler preserves them).
Types and Annotations
Attach types with ::.
- Primitives:
num,str - Logical maybe:
mbool→ true/false/maybe (runtime may treatmaybeas nondeterministic) - Arrays:
arr, generic arrays:arr<T> - Objects:
obj - Custom interfaces/classes: use their identifier
Examples:
ts
let n::num = 3
let s::str = "hello"
let maybeFlag::mbool = maybe
let flags::arr<mbool> = [true, false, maybe]
let bag::arr = [1, "a", true] # untyped array
let cfg::obj = { debug: true }Extended interface typing
- Optional fields with
?are supported:tsinterface Product { id::num; name::str; price::num; inStock::mbool; module?:: "esm" | "cjs"; // optional } - Union and string-literal types in fields are allowed (e.g.,
"esm" | "cjs" | str). - Per‑field validation at declaration checks that each provided property matches its field type (shallow).
mboolacceptstrue,false, andmaybe.- Comments are ignored by validators (lines starting with
//and blocks/* ... */).
Examples:
ts
let ok1::Product = { id: 1, name: "Laptop", price: 999.99, inStock: true, module: "cjs" };
let ok2::Product = { id: 2, name: "Mouse", price: 29.99, inStock: maybe };
let bad1::Product = { id: 3, name: "Cable", price: "9.99", inStock: true }; // error: price expects num
let bad2::Product = { id: 4, name: "Hub", price: 19.99, inStock: true, module: "commonjs" }; // error: module expects "esm" | "cjs"Mutability: mut / inmut
inmut <var>: marks an existingletvariable as immutable from that point forward (like turning it into a const). Further reassignments ormut <var> = ...become errors.mut <var> = <expr>: explicit mutation assignment, allowed only while the variable is still mutable.
Examples:
ts
let username::str = "Manuel"
let score::num = 42
mut score = 50 // allowed (score is still mutable)
inmut username // from here, username cannot change
// This will error because username is now immutable:
// mut username = "Other"Reassignment must conform to the declared type.
Functions
Use func instead of function.
ts
func Sum(a::num, b::num) {
return a + b
}
func Main(x::num = 3) {
spec.log("x:", x)
}- Default values are supported. The compiler correctly infers the type ignoring the default literal in the type signature.
- Return types are inferred; explicit annotations are optional.
Function return types
You can explicitly annotate a function's return type after the parameter list using ::ReturnType.
ts
func Sum(a::num, b::num)::num {
return a + b
}
func Wrong()::num {
return "x" //error: Return type mismatch (expects num)
}
func NoReturn()::str {
let x = 1
} // error: declares return type str but has no return- The compiler validates every
returnexpression against the declared return type. - Missing return is reported at the
::ReturnTypeposition. - When returning an object literal for an interface type, required fields are validated (shallow check):
ts
interface Person { name::str; age::num; }
func Make()::Person {
return { name: "A" } // error: expects Person (missing required: age)
}Classes
ts
class Counter {
constructor(initial::num) {
this.count::num = initial
}
inc() { this.count = this.count + 1 }
add(n::num) { this.count = this.count + n }
toString()::str { return "Counter(" + String(this.count) + ")" }
}
let c::Counter = new Counter(2)
c.inc()
c.add(5)
let s::str = c.toString() // method return type is enforcedMethod return types
- Methods can declare
::ReturnTypejust like functions. The compiler validates eachreturninside the method. - Calls to class methods use the declared return type for type inference, even when chained from
new Class(...).method().
Ternary inference and string concatenation
- The compiler infers the type of
cond ? a : bfrom its branches (not from the condition). +concatenations that include a string literal or.toString()are inferred asstr.- Common built‑ins are recognized during inference:
parseFloat/parseInt/Number → num,String/JSON.stringify → str,Boolean/Array.isArray → mbool,Math.* → num.
Control Flow
ts
let i::num = 0
for (i = 0; i < 3; i = i + 1) {
spec.log("for i:", i)
}
let w::num = 0
while (w < 2) {
spec.log("while w:", w)
w = w + 1
}
let opt::str = "B"
if (opt === "A") {
spec.log("Option A")
} else if (opt === "B") {
spec.log("Option B")
} else {
spec.log("Default")
}Interfaces
Interfaces describe object shapes.
ts
interface Person {
name::str;
age::num;
tags::arr<str>;
meta::obj;
}
func processPerson(p::Person) {
spec.log("Procesando:", p.name)
return p.name
}Errors and Code Frames
The CLI shows concise errors with code frames, including line/column and colorized output. In single‑file runs, errors show a frame for the source file.
Logging
Use spec.* for logging (see SpectralLogs doc). You can still use console.*, but the CLI will suggest spec.* for a consistent experience. A migration flag exists to rewrite automatically.
