ƿit Language
ƿit is an actor based language. It combines a familiar syntax with strict immutability semantics.
Variables and Constants
Variables are declared with var, constants with def. All declarations must be initialized and must appear at the function body level — not inside if, while, for, or do blocks.
var x = 10
var name = "pit"
var empty = null
def pi = 3.14159 // constant, cannot be reassigned
var a = 1, b = 2, c = 3 // multiple declarations
Data Types
ƿit has eight fundamental types:
- number — exact within a range the build selects. Arithmetic with no representable answer produces null. A whole number the build represents exactly is a fit number;
is_fittests it, and the range is target-dependent (see Semantics). - text — Unicode strings
- logical —
trueorfalse - null — the absence of a value
- array — ordered, numerically-indexed sequences
- record — key-value records. Keys are normally text, but records can also be used as keys — record keys are effectively private, since only code holding a reference to the exact same record can access the field.
- blob — binary data (bits, not bytes)
- function — first-class callable values
These types are always immutable: number, text, logical, null, function. These types are initially mutable but can be made permanently immutable with stone(): array, record, blob.
Reading a missing or invalid key from a record produces null. Writing to an invalid key disrupts.
Literals
// Numbers
42
3.14
-5
0
1e3 // scientific notation (1000)
// Text
"hello"
`template ${x}` // string interpolation
`${1 + 2}` // expression interpolation
// Logical
true
false
// Null
null
// Arrays
[1, 2, 3]
[]
// Records
{a: 1, b: "two"}
{}
// Regex
/\d+/
/hello/i // with flags
Operators
Arithmetic
2 + 3 // 5
5 - 3 // 2
3 * 4 // 12
12 / 4 // 3
10 % 3 // 1
Arithmetic operators work on numbers only. Give one a non-number and the result is
null — see Semantics.
& joins text, converting number operands to text first:
"item " & "count" // "item count"
"item " & 3 // "item 3"
Choosing between + and & says what you meant: + is a claim that both operands are
numbers, & that the result is text.
Null selection
| returns the first non-null value:
var a = null | 3 // 3
var b = 7 | 9 // 7
Comparison
All comparisons are strict: operands are compared as they are. Operators: ==, !=, <, >, <=, >=.
Logical
/\, \/, and !. Logical operators short-circuit:
var called = false
var fn = function() { called = true; return true }
var r = false /\ fn() // fn() not called
r = true \/ fn() // fn() not called
&& and || are accepted as compatibility spellings for the same operations, but new code should prefer /\ and \/.
Increment and Decrement
var x = 5
x++ // returns 5, x becomes 6 (postfix)
++x // returns 7, x becomes 7 (prefix)
x-- // returns 7, x becomes 6 (postfix)
--x // returns 5, x becomes 5 (prefix)
Compound Assignment
+=, -=, *=, /=, %=.
Ternary
condition ? a : b — standard ternary operator.
Removing Record Fields
Assign null to a record field to remove that key from the record.
var o = {a: 1, b: 2}
o.a = null
o.a // null
o.b // 2
Accessing a key on a record which does not exist always returns null. Assigning to an invalid key disrupts.
Property Access
Dot and Bracket
var o = {x: 10}
o.x // 10 (dot read)
o.x = 20 // dot write
o["x"] // 20 (bracket read)
var key = "x"
o[key] // 20 (computed bracket)
o["y"] = 30 // bracket write
Record as Key
Records can be used as keys in other Records.
var k = {}
var o = {}
o[k] = 42
o[k] // 42
o[{}] // null (different Record)
o[k] = null
o[k] // null
Chained Access
var d = {a: {b: [1, {c: 99}]}}
d.a.b[1].c // 99
Arrays
Arrays are distinct from Records. They are ordered, numerically-indexed sequences. Like records, accessing an illegal or non existent array index returns null; assigning to an invalid array index disrupts. Valid array indices are integers, 0 and greater.
var arr = [1, 2, 3]
arr[0] // 1
arr[2] = 10 // [1, 2, 10]
length(arr) // 3
Push and Pop
var a = [1, 2]
a[] = 3 // push: [1, 2, 3]
length(a) // 3
var v = a[] // pop: v is 3, a is [1, 2]
length(a) // 2
Control Flow
If / Else
var x = 0
if (true) x = 1
if (false) x = 2 else x = 3
if (false) x = 4
else if (true) x = 5
else x = 6
While
var i = 0
while (i < 5) i++
// break
i = 0
while (true) {
if (i >= 3) break
i++
}
// continue
var sum = 0
i = 0
while (i < 5) {
i++
if (i % 2 == 0) continue
sum += i
}
For
Variables cannot be declared in the for initializer. Declare them at the function body level.
var sum = 0
var i = 0
for (i = 0; i < 5; i++) sum += i
// break
sum = 0
i = 0
for (i = 0; i < 10; i++) {
if (i == 5) break
sum += i
}
// continue
sum = 0
i = 0
for (i = 0; i < 5; i++) {
if (i % 2 == 0) continue
sum += i
}
// nested
sum = 0
var j = 0
for (i = 0; i < 3; i++) {
for (j = 0; j < 3; j++) {
sum++
}
}
Functions
Function Expressions
var add = function(a, b) { return a + b }
add(2, 3) // 5
Return
A function with no return returns null. An early return exits immediately.
var fn = function() { var x = 1 }
fn() // null
var fn2 = function() { return 1; return 2 }
fn2() // 1
Go
go is an explicit tail call. It must be followed by a function call. The
current function is replaced by the called function, so no code after the go
statement runs in the current function.
var loop = function(n, acc) {
if (n == 0) return acc
go loop(n - 1, acc + n)
}
loop(100, 0) // 5050
Use go when the next step is simply to continue as another function call.
Use return when returning a value that has already been computed.
Arguments
Functions can have at most 4 parameters. Use a record to pass more values.
Missing arguments are null. Passing more arguments than a function accepts
disrupts; when the compiler can prove the call over-applies, it refuses the
compile instead of leaving the disruption for run time.
var fn = function(a, b) { return a + b }
fn(1, 2) // 3
fn(1, 2, 3) // refused: fn expects 2 args, called with 3
var fn2 = function(a, b) { return a }
fn2(1) // 1 (b is null)
// More than 4 parameters — use a record
var draw = function(shape, opts) {
// opts.x, opts.y, opts.color, ...
}
Closures
Functions capture variables from their enclosing scope.
var make = function(x) {
return function(y) { return x + y }
}
var add5 = make(5)
add5(3) // 8
Captured variables can be mutated:
var counter = function() {
var n = 0
return function() { n = n + 1; return n }
}
var c = counter()
c() // 1
c() // 2
Identifiers
Identifiers can contain ? and ! characters, both as suffixes and mid-name.
var nil? = function(x) { return x == null }
nil?(null) // true
nil?(42) // false
var set! = function(x) { return x + 1 }
set!(5) // 6
var is?valid = function(x) { return x > 0 }
is?valid(3) // true
var do!stuff = function() { return 42 }
do!stuff() // 42
The ? in an identifier is not confused with the ternary operator:
var nil? = function(x) { return x == null }
var a = nil?(null) ? "yes" : "no" // "yes"
Type Checking
Falsy values: false, 0, "", null. Everything else is truthy.
Regex
Regex literals are written with forward slashes, with optional flags.
var r = /\d+/
var result = extract("abc123", r)
result[0] // "123"
var ri = /hello/i
var result2 = extract("Hello", ri)
result2[0] // "Hello"
Error Handling
ƿit uses disrupt and disruption for error handling. A disrupt signals that something went wrong. The disruption block attached to a function catches it.
var safe_divide = function(a, b) {
if (b == 0) disrupt
return a / b
} disruption {
log.error("something went wrong")
}
disrupt is a bare keyword — it does not carry a value. The disruption block knows that something went wrong, but not what.
Re-raising
A disruption block can re-raise by calling disrupt again:
var outer = function() {
var inner = function() { disrupt } disruption { disrupt }
inner()
} disruption {
// caught here after re-raise
}
outer()
Testing for Disruption
var should_disrupt = function(fn) {
var caught = false
var wrapper = function() {
fn()
} disruption {
caught = true
}
wrapper()
return caught
}
If an actor has an unhandled disruption, it crashes.