Files
cell/docs/language.md

12 KiB

title, description, weight, type
title description weight type
ƿit Language Syntax, types, operators, and built-in functions 10 docs

ƿit is a scripting language for actor-based programming. It combines a familiar syntax with a prototype-based object system and strict immutability semantics.

Basics

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 — DEC64 decimal floating point (no rounding errors)
  • text — Unicode strings
  • logicaltrue or false
  • null — the absence of a value (no undefined)
  • array — ordered, numerically-indexed sequences
  • object — key-value records with prototype inheritance
  • blob — binary data (bits, not bytes)
  • function — first-class callable values

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]
[]

// Objects
{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
2 ** 3     // 8 (exponentiation)

Comparison

All comparisons are strict — there is no type coercion.

5 == 5     // true
5 != 6     // true
3 < 5      // true
5 > 3      // true
3 <= 3     // true
5 >= 5     // true

Logical

true && true     // true
true && false    // false
false || true    // true
false || false   // false
!true            // false
!false           // true

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

Bitwise

5 & 3       // 1   (AND)
5 | 3       // 7   (OR)
5 ^ 3       // 6   (XOR)
~0          // -1  (NOT)
1 << 3      // 8   (left shift)
8 >> 3      // 1   (right shift)
-1 >>> 1    // 2147483647 (unsigned right shift)

Unary

+5          // 5
-5          // -5
-(-5)       // 5

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

var x = 10
x += 3      // 13
x -= 3      // 10
x *= 2      // 20
x /= 4     // 5
x %= 3      // 2

Ternary

var a = true ? 1 : 2       // 1
var b = false ? 1 : 2      // 2
var c = true ? (false ? 1 : 2) : 3  // 2 (nested)

Comma

The comma operator evaluates all expressions and returns the last.

var x = (1, 2, 3)  // 3

In

Test whether a key exists in an object.

var o = {a: 1}
"a" in o    // true
"b" in o    // false

Delete

Remove a key from an object.

var o = {a: 1, b: 2}
delete o.a
"a" in o    // false
o.b         // 2

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

Object as Key

Objects can be used as keys in other objects.

var k = {}
var o = {}
o[k] = 42
o[k]          // 42
o[{}]         // null (different object)
k in o        // true
delete o[k]
k in o        // false

Chained Access

var d = {a: {b: [1, {c: 99}]}}
d.a.b[1].c    // 99

Arrays

Arrays are distinct from objects. They are ordered, numerically-indexed sequences.

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

Objects

Objects are key-value records with prototype-based inheritance.

var point = {x: 10, y: 20}
point.x             // 10
point["y"]          // 20

Prototypes

// Create object with prototype
var parent = {x: 10}
var child = meme(parent)
child.x             // 10 (inherited)
proto(child)        // parent

// Override does not mutate parent
child.x = 20
parent.x            // 10

Mixins

var p = {a: 1}
var m1 = {b: 2}
var m2 = {c: 3}
var child = meme(p, [m1, m2])
child.a             // 1 (from prototype)
child.b             // 2 (from mixin)
child.c             // 3 (from mixin)

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

Arrow Functions

var double = x => x * 2
double(5)           // 10

var sum = (a, b) => a + b
sum(2, 3)           // 5

var block = x => {
  var y = x * 2
  return y + 1
}
block(5)            // 11

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

Arguments

Functions can have at most 4 parameters. Use a record to pass more values.

Extra arguments are ignored. Missing arguments are null.

var fn = function(a, b) { return a + b }
fn(1, 2, 3)        // 3 (extra arg ignored)

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, ...
}

Immediately Invoked Function Expression

var r = (function(x) { return x * 2 })(21)  // 42

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

Recursion

var fact = function(n) {
  if (n <= 1) return 1
  return n * fact(n - 1)
}
fact(5)             // 120

This Binding

When a function is called as a method, this refers to the object.

var obj = {
  val: 10,
  get: function() { return this.val }
}
obj.get()           // 10

Currying

var f = function(a) {
  return function(b) {
    return function(c) { return a + b + c }
  }
}
f(1)(2)(3)          // 6

Identifiers

Identifiers can contain ? and ! characters, both as suffixes and mid-name.

var nil? = (x) => x == null
nil?(null)          // true
nil?(42)            // false

var set! = (x) => x + 1
set!(5)             // 6

var is?valid = (x) => x > 0
is?valid(3)         // true

var do!stuff = () => 42
do!stuff()          // 42

The ? in an identifier is not confused with the ternary operator:

var nil? = (x) => x == null
var a = nil?(null) ? "yes" : "no"   // "yes"

Type Checking

Type Functions

is_number(42)           // true
is_text("hi")           // true
is_logical(true)        // true
is_object({})           // true
is_array([])            // true
is_function(function(){})  // true
is_null(null)           // true
is_object([])           // false (array is not object)
is_array({})            // false (object is not array)

Truthiness

Falsy values: false, 0, "", null. Everything else is truthy.

if (0) ...              // not entered
if ("") ...             // not entered
if (null) ...           // not entered
if (1) ...              // entered
if ("hi") ...           // entered
if ({}) ...             // entered
if ([]) ...             // entered

Immutability with Stone

The stone() function makes values permanently immutable.

var o = {x: 1}
is_stone(o)             // false
stone(o)
is_stone(o)             // true
o.x = 2                 // disrupts!

Stone is deep — all nested objects and arrays are also frozen. This cannot be reversed.

Function Proxy

A function with two parameters (name, args) acts as a proxy when properties are accessed on it. Any method call on the function dispatches through the proxy.

var proxy = function(name, args) {
  return `${name}:${length(args)}`
}
proxy.hello()           // "hello:0"
proxy.add(1, 2)         // "add:2"
proxy["method"]()       // "method:0"
var m = "dynamic"
proxy[m]()              // "dynamic:0"

For non-proxy functions, property access disrupts:

var fn = function() { return 1 }
fn.foo                  // disrupts
fn.foo = 1              // disrupts

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 {
  print("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.

Self-Referencing Structures

Objects can reference themselves:

var o = {name: "root"}
o.self = o
o.self.self.name        // "root"

Variable Shadowing

Inner functions can shadow outer variables:

var x = 10
var fn = function() {
  var x = 20
  return x
}
fn()                    // 20
x                       // 10