Skip to content

Reference

Civet code on the lefttop, compiled TypeScript output on the rightbottom.

In most cases, the Civet code on the lefttop is optional shorthand. The TypeScript code on the rightbottom (and most TypeScript code) is almost always also valid Civet input.

Variable Declaration

By default, you are responsible for declaring your variables via var, let, const, or their shorthands:

a := 10
b .= 10
c: number | string .= 0
let d: boolean
var v: any
Edit inline or edit in the Playground!
const a = 10;
let b = 10;
let c: number | string = 0;
let d: boolean;
var v: any;

Alternatively, you can use a "civet" directive at the beginning of your file to specify one of two automatic variable declaration modes:

autoVar

"civet autoVar"
sos = 0
for item of iterable
  square = item * item
  sos += square
Edit inline or edit in the Playground!
var sos, square;
sos = 0;
for (const item of iterable) {
  square = item * item;
  sos += square;
}

autoLet

"civet autoLet"
sos = 0
for item of iterable
  square = item * item
  sos += square
Edit inline or edit in the Playground!
let sos = 0;
for (const item of iterable) {
  let square = item * item;
  sos += square;
}

autoConst

"civet autoConst"
let sos = 0
for item of iterable
  square = item * item
  sos += square
Edit inline or edit in the Playground!
let sos = 0;
for (const item of iterable) {
  const square = item * item;
  sos += square;
}

Globals

You can prevent autoVar, autoLet, and autoConst from declaring certain variables by specifying a list of globals in the "civet" directive:

"civet autoVar globals=cache,version"
cache = {}
version = '1.2.3'
size = 1024
Edit inline or edit in the Playground!
var size;
cache = {};
version = "1.2.3";
size = 1024;

Declarations in Conditions and Loops

if match := regex.exec string
  console.log match[1], match[2]
Edit inline or edit in the Playground!
let ref;
if ((ref = regex.exec(string))) {
  const match = ref;
  console.log(match[1], match[2]);
}
if [, dir, base] := /^(.*\/)?([^/]*)$/.exec file
  console.log dir, base
Edit inline or edit in the Playground!
function len<
  T extends readonly unknown[],
  N extends number,
>(arr: T, length: N): arr is T & { length: N } {
  return arr.length === length;
}
let ref;
if (
  (ref = /^(.*\/)?([^/]*)$/.exec(file)) &&
  Array.isArray(ref) &&
  len(ref, 3)
) {
  const [, dir, base] = ref;
  console.log(dir, base);
}

Note that array lengths must match exactly because this is a form of pattern matching.

if {x, y} := getLocation()
  console.log `At ${x}, ${y}`
else
  console.log "Not anywhere"
Edit inline or edit in the Playground!
let ref;
if (
  (ref = getLocation()) &&
  typeof ref === "object" &&
  "x" in ref &&
  "y" in ref
) {
  const { x, y } = ref;
  console.log(`At ${x}, ${y}`);
} else {
  console.log("Not anywhere");
}
node .= linkedList.head
while {data, next} := node
  console.log data
  node = next
Edit inline or edit in the Playground!
let node = linkedList.head;
while (
  node &&
  typeof node === "object" &&
  "data" in node &&
  "next" in node
) {
  const { data, next } = node;
  console.log(data);
  node = next;
}

You can check for non-null instead of truthy values with a ?:

sum .= 0
while number? := next()
  sum += number
Edit inline or edit in the Playground!
let sum = 0;
let ref;
while ((ref = next()) != null) {
  const number = ref;
  sum += number;
}

The negated form until exposes the declaration after the block instead of inside it:

until {status: "OK", data} := attempt()
console.log data
Edit inline or edit in the Playground!
let ref;
while (
  !(
    (ref = attempt()) &&
    typeof ref === "object" &&
    "status" in ref &&
    ref.status === "OK" &&
    "data" in ref
  )
);
const { status, data } = ref;
console.log(data);

The negated form of if, unless, always exposes the declaration to an else block (if present). It also exposes the declaration to after the unless block provided the "then" block contains a guaranteed "exit" statement such as return or throw. This is useful for guard checks:

unless item? := getItem() then return
unless {x, y} := item.getCoords()
  throw new Error "Item missing coordinates"
console.log `(${x}, ${y})`
Edit inline or edit in the Playground!
let ref;
if (!((ref = getItem()) != null)) return;
const item = ref;
let ref1;
if (
  !(
    (ref1 = item.getCoords()) &&
    typeof ref1 === "object" &&
    "x" in ref1 &&
    "y" in ref1
  )
) {
  throw new Error("Item missing coordinates");
}
const { x, y } = ref1;
console.log(`(${x}, ${y})`);

Objects

Unbraced Literals

When every property has a value, braces can be omitted.

person := name: 'Henry', age: 4
obj :=
  a: 1
  b: 2
  c:
    x: 'pretty'
    y: 'cool'
Edit inline or edit in the Playground!
const person = { name: "Henry", age: 4 };
const obj = {
  a: 1,
  b: 2,
  c: {
    x: "pretty",
    y: "cool",
  },
};

Braced Literals

With braces, the {x} shorthand generalizes to any sequence of member accesses and/or calls and/or unary operators:

another := {person.name, obj?.c?.x}
computed := {foo(), bar()}
named := {lookup[x+y]}
cast := {value as T}
bool := {!!available}
Edit inline or edit in the Playground!
const another = {
  name: person.name,
  x: obj?.c?.x,
};
const computed = { foo: foo(), bar: bar() };
let ref;
const named = {
  [((ref = x + y), ref)]: lookup[ref],
};
const cast = { value: value as T };
const bool = { available: !!available };

To avoid the trailing } in a braced object literal, you can use {} followed by its properties (as if they were arguments in an implicit function application):

obj := {}
  a: 1
  b
  person.name
  method()
    console.log "hi"
Edit inline or edit in the Playground!
const obj = {
  a: 1,
  b,
  name: person.name,
  method() {
    return console.log("hi");
  },
};

Property Names

Both braced and unbraced literals support shorthand for computed property names:

negate := {-1: +1, +1: -1}
templated :=
  `${prefix}${suffix}`: result
headers :=
  Content-Type: "text/html"
  Content-Encoding: "gzip"
Edit inline or edit in the Playground!
const negate = { [-1]: +1, [+1]: -1 };
const templated = {
  [`${prefix}${suffix}`]: result,
};
const headers = {
  "Content-Type": "text/html",
  "Content-Encoding": "gzip",
};

Object Globs

Inspired by brace expansion in shell globs:

point = data{x,y}
point = data.{x,y}
point.{x,y} = data
point3D = { point.{x,y}, z: 0 }
complex := obj.{x:a, b.c()?.y}
merged := data.{...global, ...user}
data.{a, b, ...rest} = result
Edit inline or edit in the Playground!
point = { x: data.x, y: data.y };
point = { x: data.x, y: data.y };
({ x: point.x, y: point.y } = data);
point3D = { x: point.x, y: point.y, z: 0 };
const complex = { x: obj.a, y: obj.b.c()?.y };
const merged = { ...data.global, ...data.user };
({ a: data.a, b: data.b, ...data.rest } = result);

Flagging Shorthand

Inspired by LiveScript:

config := {
  +debug
  -live
  !verbose
}
Edit inline or edit in the Playground!
const config = {
  debug: true,
  live: false,
  verbose: false,
};

Methods and Getters/Setters

Braced objects support methods and getters/setters:

p := {
  name: 'Mary'
  say(msg)
    console.log @name, 'says', msg
  setName(@name);
  get NAME()
    @name.toUpperCase()
}
p.say p.NAME
Edit inline or edit in the Playground!
const p = {
  name: "Mary",
  say(msg) {
    return console.log(this.name, "says", msg);
  },
  setName(name1) {
    this.name = name1;
  },
  get NAME() {
    return this.name.toUpperCase();
  },
};
p.say(p.NAME);

TIP

Methods need a body, or they get treated as literal shorthand. To specify a blank body, use ; or {}.

Property Access

Many more literals can appear after a . to access an object property:

json.'long property'
json.`${movie} name`
matrix.0.0
array.-1
Edit inline or edit in the Playground!
json["long property"];
json[`${movie} name`];
matrix[0][0];
array[array.length - 1];

You can omit the . in ?. and !. property access:

json?data?value
account!name?first
Edit inline or edit in the Playground!
json?.data?.value;
account!.name?.first;

You can also write property access as an English possessive (inspired by _hyperscript):

mario's brother's name
mario?'s name
json's "long property"'s `${movie} name`
Edit inline or edit in the Playground!
mario.brother.name;
mario?.name;
json["long property"][`${movie} name`];

Length Shorthand

The property access .# or just # is short for .length:

array.#
array#
"a string also".#
Edit inline or edit in the Playground!
array.length;
array.length;
"a string also".length;

On its own, # is shorthand for this.length:

class List
  push(item)
    @[#] = item
  wrap(index)
    @[index %% #]
Edit inline or edit in the Playground!
var modulo = ((a: number, b: number) =>
  ((a % b) + b) % b) as ((
  a: number,
  b: number,
) => number) &
  ((a: bigint, b: bigint) => bigint);
class List {
  push(item) {
    return (this[this.length] = item);
  }
  wrap(index) {
    return this[modulo(index, this.length)];
  }
}

# in checks for the "length" property:

# in x
Edit inline or edit in the Playground!
"length" in x;

#: defines the "length" property in an object literal:

array := {0: 'a', #: 1}
Edit inline or edit in the Playground!
const array = { 0: "a", length: 1 };

Length shorthand looks and behaves similar to private fields, with the exception that .length is not private.

Trailing Property Access

A . or ?. property access can trail on another line, at the same or deeper indentation:

getItems url
.filter .match
.sort()
Edit inline or edit in the Playground!
getItems(url)
  .filter(($) => $.match)
  .sort();
document
  ?.querySelectorAll pattern
  .forEach (element) =>
    element.style.color = 'purple'
Edit inline or edit in the Playground!
document
  ?.querySelectorAll(pattern)
  .forEach((element) => {
    return (element.style.color = "purple");
  });

Arrays

Bracketed

Commas are optional at the ends of lines.

rotate := [
  c, -s
  s, c
]
Edit inline or edit in the Playground!
const rotate = [c, -s, s, c];
func.apply @, [
  arg1
  arg2
]
Edit inline or edit in the Playground!
func.apply(this, [arg1, arg2]);
people := [
  name: "Alice"
  id: 7
,
  name: "Bob"
  id: 9
]
Edit inline or edit in the Playground!
const people = [
  { name: "Alice", id: 7 },
  { name: "Bob", id: 9 },
];

To avoid the trailing ] in a bracketed array literal, you can use [] followed by its elements (as if they were arguments in an implicit function application):

rotate := []
  c, -s
  s, c
func.apply @, []
  arg1
  arg2
Edit inline or edit in the Playground!
const rotate = [c, -s, s, c];
func.apply(this, [arg1, arg2]);

Bulleted

Instead of brackets, array items can be specified via . or bullets:

rotate :=
  . c, -s
  . s, c
Edit inline or edit in the Playground!
const rotate = [c, -s, s, c];
func.apply @,
  • arg1
  • arg2
Edit inline or edit in the Playground!
func.apply(this, [arg1, arg2]);
people :=
  . name: "Alice"
    id: 7
  . name: "Bob"
    id: 9
Edit inline or edit in the Playground!
const people = [
  { name: "Alice", id: 7 },
  { name: "Bob", id: 9 },
];

You can nest bulleted arrays, and list multiple items per line via ,:

colorPairs :=
  . . "red"
    . "#f00"
  . . "green"
    . "#0f0"
  . . "blue", "#00f"
Edit inline or edit in the Playground!
const colorPairs = [
  ["red", "#f00"],
  ["green", "#0f0"],
  ["blue", "#00f"],
];

INFO

Bulleted arrays generally need to start on their own line with an indentation. The only current exception is for nested bulleted arrays.

Rest

Rest properties/parameters/elements are no longer limited to the final position. You may use them in their first or middle positions as well.

[...head, last] = [1, 2, 3, 4, 5]
Edit inline or edit in the Playground!
([...head] = [1, 2, 3, 4, 5]),
  ([last] = head.splice(-1));
{a, ...rest, b} = {a: 7, b: 8, x: 0, y: 1}
Edit inline or edit in the Playground!
({ a, b, ...rest } = { a: 7, b: 8, x: 0, y: 1 });
function justDoIt(a, ...args, cb) {
  cb.apply(a, args)
}
Edit inline or edit in the Playground!
function justDoIt(a, ...args) {
  let [cb] = args.splice(-1);
  return cb.apply(a, args);
}

You can also omit the name of the rest component:

[first, ..., last] = array
Edit inline or edit in the Playground!
let ref;
([first, ...ref] = array),
  ([last] = ref.splice(-1));

Range Literals

[x..y] includes x and y, while [x...y] includes x but not y (as in Ruby and CoffeeScript).

letters := ['a'..'f']
numbers := [1..10]
indices := [0...array.length]
Edit inline or edit in the Playground!
const letters = ["a", "b", "c", "d", "e", "f"];
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const indices = ((s) =>
  Array.from(
    { length: array.length - s },
    (_, i) => s + i,
  ))(0);

An infinite range [x..] is supported when looping.

Alternatively, .. can exclude an endpoint with <, or explicitly include an endpoint with <= (or ):

indices := [0..<array.length]
strict := [a<..<b]
// [a<=..≤b] same as [a..b]
Edit inline or edit in the Playground!
const indices = ((s) =>
  Array.from(
    { length: array.length - s },
    (_, i) => s + i,
  ))(0);
const strict = ((s) =>
  Array.from(
    { length: b - s - 1 },
    (_, i) => s + i,
  ))(a + 1);
// [a<=..≤b] same as [a..b]

You can construct a reverse (decreasing) range by specifying > (exclusive) or >= (inclusive) on at least one endpoint:

reversed := [10..>=1]
reversedIndices := [array.length>..0]
Edit inline or edit in the Playground!
const reversed = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const reversedIndices = ((s) =>
  Array.from({ length: s - 0 }, (_, i) => s - i))(
  array.length - 1,
);

If you'd rather [a..b] and [a...b] construct an increasing or decreasing range depending on whether a < b or a > b (unless you specify an explicit direction via </>/<=/>=), use the "civet coffeeRange" directive.

Array/String Slicing

[i..j] includes i and j, while [i...j] includes i but not j. i and/or j can be omitted when slicing.

start := numbers[..2]
mid := numbers[3...-2]
end := numbers[-2..]
numbers[1...-1] = []
Edit inline or edit in the Playground!
const start = numbers.slice(0, 2 + 1 || 1 / 0);
const mid = numbers.slice(3, -2);
const end = numbers.slice(-2);
numbers.splice(1, -1 - 1, ...[]);

Alternatively, you can exclude or include endpoints using .. with < or <=:

strict := numbers[first<..<last]
Edit inline or edit in the Playground!
const strict = numbers.slice(first + 1, last);

Slices are increasing by default, but you can reverse them with > or >=:

reversed := x[..>=]
Edit inline or edit in the Playground!
type RSliceable<R> =
  | string
  | {
      length: number;
      slice(
        start: number,
        end: number,
      ): { reverse(): R };
    };
var rslice: <R, T extends string | RSliceable<R>>(
  a: T,
  start?: number,
  end?: number,
) => T extends string
  ? string
  : T extends RSliceable<infer R>
    ? R
    : never = (a, start = -1, end = -1) => {
  const l = a.length;
  if (start < 0) start += l;
  if (++end < 0) end += l;
  if (typeof a === "string") {
    let r = "";
    if (start > l - 1) start = l - 1;
    if (end < 0) end = 0;
    for (let i = start; i >= end; --i) r += a[i];
    return r as any;
  } else {
    return a.slice(end, start + 1).reverse();
  }
};
const reversed = rslice(x, -1, 0 - 1);

If you just want to specify one endpoint of an increasing slice, you can avoid .. altogether:

[left, right] = [x[<=i], x[>i]]
[left, right] = [x[<i], x[>=i]]
Edit inline or edit in the Playground!
[left, right] = [
  x.slice(0, i + 1),
  x.slice(i + 1),
];
[left, right] = [x.slice(0, i), x.slice(i)];

Modulo Indexing

You can use a[i%] to index into an array modulo its length:

for i of [0...a#]
  drawAngle v[i-1%], v[i], v[i+1%]
Edit inline or edit in the Playground!
var modulo = ((a: number, b: number) =>
  ((a % b) + b) % b) as ((
  a: number,
  b: number,
) => number) &
  ((a: bigint, b: bigint) => bigint);
for (let end = a.length, i1 = 0; i1 < end; ++i1) {
  const i = i1;
  drawAngle(
    v[modulo(i - 1, v.length)],
    v[i],
    v[modulo(i + 1, v.length)],
  );
}

See also length shorthand.

Strings

Strings can span multiple lines:

console.log "Hello,
world!"
Edit inline or edit in the Playground!
console.log("Hello,\nworld!");

Triple-Quoted Strings

Leading indentation is removed.

console.log '''
  <div>
    Civet
  </div>
'''
Edit inline or edit in the Playground!
console.log(`<div>
  Civet
</div>`);
console.log """
  <div>
    Civet #{version}
  </div>
"""
Edit inline or edit in the Playground!
console.log(`<div>
  Civet ${version}
</div>`);
console.log ```
  <div>
    Civet ${version}
  </div>
```
Edit inline or edit in the Playground!
console.log(`<div>
  Civet ${version}
</div>`);

Regular Expressions

Civet supports JavaScript regular expression literals /.../, provided the first slash is not immediately followed by a space. Instead of / x / (which can be interpreted as division in some contexts), write /\ x / or /[ ]x / (or more escaped forms like /[ ]x[ ]/).

In addition, you can use ///.../// to write multi-line regular expressions that ignore top-level whitespace and single-line comments:

phoneNumber := ///
  ^
  \+? ( \d [\d-. ]+ )?  // country code
  ( \( [\d-. ]+ \) )?   // area code
  (?=\d) [\d-. ]+ \d    // start and end with digit
  $
///
Edit inline or edit in the Playground!
const phoneNumber =
  /^\+?(\d[\d-. ]+)?(\([\d-. ]+\))?(?=\d)[\d-. ]+\d$/;

INFO

/// is treated as a comment if it appears at the top of your file, to support TypeScript triple-slash directives. Keep this in mind when trying examples in the Playground.

Symbols

:symbol represents either a well-known symbol (static member of Symbol) or a consistently generated symbol in the global symbol registry via Symbol.for:

magicSymbol := :magic
iterable = {
  :iterator()
    yield 1
    yield 2
    yield 3
  :isConcatSpreadable: true
}
iterable.:iterator()
Edit inline or edit in the Playground!
const magicSymbol = Symbol.for("magic");
iterable = {
  *[Symbol.iterator]() {
    yield 1;
    yield 2;
    yield 3;
  },
  [Symbol.isConcatSpreadable]: true,
};
iterable[Symbol.iterator]();

In case you're building for a special environment, you can set the list of well-known symbols via a compiler directive:

"civet symbols=magic"
magicSymbol := :magic
iteratorSymbol := :iterator
Edit inline or edit in the Playground!
const magicSymbol = Symbol.magic;
const iteratorSymbol = Symbol.for("iterator");

Symbol names that aren't valid identifiers can be wrapped in quotes:

magicSymbol := :"magic-symbol"
Edit inline or edit in the Playground!
const magicSymbol = Symbol.for("magic-symbol");

Operators

All JavaScript/TypeScript Operators

center := min + length / 2
name := user?.name ?? defaultName
typeof x === "string" && x += "!"
result! as string | number
Edit inline or edit in the Playground!
const center = min + length / 2;
const name = user?.name ?? defaultName;
typeof x === "string" && (x += "!");
result! as string | number;

INFO

Civet is a bit sensitive when it comes to spacing around operators. Unary symbol operators (+, -, ~, !) must not have spaces after them. Binary symbol operators should either have spaces on both sides, or no space on either side.

Late Assignment

a + b = c
Edit inline or edit in the Playground!
a + (b = c);

Multi Assignment

(count[key] ?= 0)++
(count[key] ?= 0) += 1
++count *= 2
Edit inline or edit in the Playground!
(count[key] ??= 0), count[key]++;
(count[key] ??= 0), (count[key] += 1);
++count, (count *= 2);

Humanized Operators

a is b
a is not b
a and b
a or b
a not in b
a not instanceof b
a !in b
a !instanceof b
a?
Edit inline or edit in the Playground!
a === b;
a !== b;
a && b;
a || b;
!(a in b);
!(a instanceof b);
!(a in b);
!(a instanceof b);
a != null;

Includes Operator

item is in array
item is not in array
substring is in string
item1 ∈ container ∌ item2  // Unicode
Edit inline or edit in the Playground!
array.includes(item);
!array.includes(item);
string.includes(substring);
container.includes(item1) &&
  !container.includes(item2); // Unicode

Concat Operator

a ++ b ++ c
[1,2,3] ⧺ rest
Edit inline or edit in the Playground!
a.concat(b).concat(c);
[1, 2, 3].concat(rest);

You can use ++ to concatenate arrays or strings, or your own types by providing a concat method. Remember that Array.prototype.concat appends a single item unless it is an array (or has the Symbol.isConcatSpreadable property), in which case it flattens it into the target array. (The right-hand side also needs to offer the array-like interface: length and indexed access.) Civet's assignment operator behaves the same:

a ++= b
Edit inline or edit in the Playground!
var concatAssign: <
  B,
  A extends
    | { push: (this: A, b: B) => void }
    | (B extends unknown[]
        ? { push: (this: A, ...b: B) => void }
        : never),
>(
  lhs: A,
  rhs: B,
) => A = (lhs, rhs) => (
  (rhs as any)?.[Symbol.isConcatSpreadable] ??
  Array.isArray(rhs)
    ? (lhs as any).push.apply(lhs, rhs as any)
    : (lhs as any).push(rhs),
  lhs
);
concatAssign(a, b);

Assignment Operators

a and= b
a or= b
a ?= b
obj.key ?= "civet"
Edit inline or edit in the Playground!
a &&= b;
a ||= b;
a ??= b;
obj.key ??= "civet";

Optional Chaining

obj?prop
obj?[key]
fun?(arg)
Edit inline or edit in the Playground!
obj?.prop;
obj?.[key];
fun?.(arg);

Optional Chain Assignment

obj?prop = value
obj?[key] = value
fun?(arg).prop = value
fun?(arg)?prop?[key] = value
Edit inline or edit in the Playground!
obj != null ? (obj.prop = value) : void 0;
obj != null ? (obj[key] = value) : void 0;
fun != null ? (fun(arg).prop = value) : void 0;
let ref;
fun != null &&
(ref = fun(arg)) != null &&
(ref = ref.prop) != null
  ? (ref[key] = value)
  : void 0;

Existence Checking

x?
x.y[z]?
not x?
x? + y?
Edit inline or edit in the Playground!
x != null;
x.y[z] != null;
x == null;
(x != null) + (y != null);

Chained Comparisons

a < b <= c
a ≤ b ≤ c  // Unicode
a ≡ b ≣ c ≠ d ≢ e
a is b is not c
0 <= a? < n
a instanceof b not instanceof c
x? instanceof Function
Edit inline or edit in the Playground!
a < b && b <= c;
a <= b && b <= c; // Unicode
a == b && b === c && c != d && d !== e;
a === b && b !== c;
a != null && 0 <= a && a < n;
a instanceof b && !(b instanceof c);
x != null && x instanceof Function;

Prefix Operators

Complex nested conditions can be written prefix-style by wrapping the binary operators in parentheses:

function haveAccess(doc, user)
  (and)
    user?
    (or)
      user.super, doc.worldReadable
      user.name is doc.owner
      (and)
        doc.groupReadable
        user.group is doc.group
Edit inline or edit in the Playground!
function haveAccess(doc, user) {
  return (
    user != null &&
    (user.super ||
      doc.worldReadable ||
      user.name === doc.owner ||
      (doc.groupReadable &&
        user.group === doc.group))
  );
}

This is a special case of binary operators as functions followed by immediate function calls. In this special case, the operator can be given multiple arguments, and the operators short circuit as usual.

instanceof shorthand

a <? b
a !<? b
a <? b !<? c
Edit inline or edit in the Playground!
a instanceof b;
!(a instanceof b);
a instanceof b && !(b instanceof c);

typeof shorthand

a <? "string"
a !<? "string"
a instanceof "number"
a not instanceof "number"
a !instanceof "number"
Edit inline or edit in the Playground!
typeof a === "string";
typeof a !== "string";
typeof a === "number";
typeof a !== "number";
typeof a !== "number";

Logical XOR Operator

a ^^ b
a xor b
a ^^= b
a xor= b
Edit inline or edit in the Playground!
type Falsy = false | 0 | '' | 0n | null | undefined;
var xor: <A, B>(a: A, b: B) => A extends Falsy ? B : B extends Falsy ? A : (false | (A & Falsy extends never ? never : B) | (B & Falsy extends never ? never : A)) = (a, b) => (a ? !b && a : b) as any;
xor(a, b)
xor(a, b)
a = xor(a, b)
a = xor(a, b)
a !^ b
a xnor b
a !^= b
a xnor= b
Edit inline or edit in the Playground!
type Falsy = false | 0 | '' | 0n | null | undefined;
var xnor: <A, B>(a: A, b: B) => A & Falsy extends never ? B : (true | (B extends Falsy ? never : A) | (A extends Falsy ? never : B)) = (a, b) => (a ? b : !b || a) as any;
xnor(a, b)
xnor(a, b)
a = xnor(a, b)
a = xnor(a, b)

Integer Division and Modulo Operator

%/ or ÷ is integer division (like // in some languages).

let a = -3
let b = 5
let frac = a / b // -0.6
let div = a %/ b // -1
console.log frac, div
Edit inline or edit in the Playground!
var div1: (a: number, b: number) => number = (
  a,
  b,
) => Math.floor(a / b);
let a = -3;
let b = 5;
let frac = a / b; // -0.6
let div = div1(a, b); // -1
console.log(frac, div);

% can return negative values, while %% is always between 0 and the divisor.

let a = -3
let b = 5
let rem = a % b // -3
let mod = a %% b // 2
console.log rem, mod
Edit inline or edit in the Playground!
var modulo = ((a: number, b: number) =>
  ((a % b) + b) % b) as ((
  a: number,
  b: number,
) => number) &
  ((a: bigint, b: bigint) => bigint);
let a = -3;
let b = 5;
let rem = a % b; // -3
let mod = modulo(a, b); // 2
console.log(rem, mod);

Together, these operators implement the division theorem:

let a = -3
let b = 5
console.assert a === (a %/ b) * b + a %% b
Edit inline or edit in the Playground!
var div: (a: number, b: number) => number = (
  a,
  b,
) => Math.floor(a / b);
var modulo = ((a: number, b: number) =>
  ((a % b) + b) % b) as ((
  a: number,
  b: number,
) => number) &
  ((a: bigint, b: bigint) => bigint);
let a = -3;
let b = 5;
console.assert(
  a === div(a, b) * b + modulo(a, b),
);

Object.is

The "civet objectIs" directive changes the behavior of the is operator to Object.is, which is a bit better behaved than ===. The plan is to make this the default behavior, once TypeScript supports type narrowing with Object.is as well as it does for ===. (Currently, a is b will not correctly narrow b in some edge cases.)

"civet objectIs"
a is b
a is not b
Edit inline or edit in the Playground!
var is: {
  <B, A extends B>(a: A, b: B): b is A;
  <A, B>(a: A, b: B): a is A & B;
} = Object.is as any;
is(a, b);
!is(a, b);

Pipe Operator

Based on F# pipes and TC39 Proposal: Pipe Operator.

data
  |> Object.keys
  |> console.log
Edit inline or edit in the Playground!
console.log(Object.keys(data));

Pairs particularly well with single-argument function shorthand and binary operator sections:

x.length |> & + 1 |> .toString()
Edit inline or edit in the Playground!
(x.length + 1).toString();
x.length |> (+ 1) |> .toString()
Edit inline or edit in the Playground!
(x.length + 1).toString();

Build functions by starting with &:

& |> .toString |> console.log
Edit inline or edit in the Playground!
($) => console.log($.toString);
&: number |> (+ 1) |> (* 2) |> Math.round
Edit inline or edit in the Playground!
($: number) => Math.round(($ + 1) * 2);

Use as T to cast types in your pipeline:

data |> JSON.parse |> as MyRecord |> addRecord
Edit inline or edit in the Playground!
addRecord(JSON.parse(data) as MyRecord);

Use await, throw, yield, or return in your pipeline:

fetch url |> await
|> .json() |> await
|> return
Edit inline or edit in the Playground!
return await(await fetch(url)).json();

Pipe assignment:

data |>= .content
Edit inline or edit in the Playground!
data = data.content;

Fat pipes ||> pass the left-hand value to the next two steps in the pipeline (ignoring the output from the right-hand side):

array
||> .pop()
||> .push 5
||> .sort()
||> .reverse()
Edit inline or edit in the Playground!
array.pop(),
  array.push(5),
  array.sort(),
  array.reverse(),
  array;
count |> & + 1
||> console.log
|> & * 2
||> console.log
Edit inline or edit in the Playground!
let ref;
console.log((ref = count + 1)),
  console.log((ref = ref * 2)),
  ref;
url |> fetch |> await
||> (response) => console.log response.status
|> .json() |> await
||> (json) => console.log "json:", json
|> callback
Edit inline or edit in the Playground!
let ref;
((response) => console.log(response.status))(
  (ref = await fetch(url)),
),
  ((json) => console.log("json:", json))(
    (ref = await ref.json()),
  ),
  callback(ref);
document.createElement('div')
||> .className = 'civet'
||> .appendChild document.createTextNode 'Civet'
Edit inline or edit in the Playground!
let ref;
((ref = document.createElement("div")).className =
  "civet"),
  ref.appendChild(
    document.createTextNode("Civet"),
  ),
  ref;

Unicode forms:

data ▷= func1 |▷ func2 ▷ func3
Edit inline or edit in the Playground!
let ref;
(data = func2((ref = func1(data)))), func3(ref);

Await Operators

TC39 proposal: await operations

await.allSettled promises
Edit inline or edit in the Playground!
await Promise.allSettled(promises);
await.all
  for url of urls
    fetch url
Edit inline or edit in the Playground!
await Promise.all(
  (() => {
    const results = [];
    for (const url of urls) {
      results.push(fetch(url));
    }
    return results;
  })(),
);
await.all
  for url of urls
    async do
      fetch url |> await |> .json() |> await
Edit inline or edit in the Playground!
await Promise.all(
  (() => {
    const results = [];
    for (const url of urls) {
      results.push(
        (async () => {
          {
            return await (
              await fetch(url)
            ).json();
          }
        })(),
      );
    }
    return results;
  })(),
);

Both await and await operators support an indented application form. Multiple arguments automatically get bundled into an array:

// Sequential
await
  first()
  second()
// Parallel
await.all
  first()
  second()
Edit inline or edit in the Playground!
// Sequential
[await first(), await second()];
// Parallel
await Promise.all([first(), second()]);

Alternatively, you can use array literals:

// Sequential
await [ first(), second() ]
await
  . first()
  . second()
// Parallel
await.all [ first(), second() ]
await.all
  . first()
  . second()
Edit inline or edit in the Playground!
// Sequential
[await first(), await second()];
[await first(), await second()];
// Parallel
await Promise.all([first(), second()]);
await Promise.all([first(), second()]);

Custom Infix Operators

You can also define your own infix operators; see Functions as Infix Operators below.

Unicode Operators

Many operators have Unicode equivalents. Here is a table of all currently supported:

UnicodeASCIIUnicodeASCIIUnicodeASCIIUnicodeASCII
<=>=!===
===!==:=??
||<<>>>>>
.....is inis not in
|>->=>’s's
++--÷%/

Functions

Function Calls

The parentheses in a function call are usually optional. If present, there should be no space between the function and the open paren.

console.log x, f(x), (f g x), g f x
Edit inline or edit in the Playground!
console.log(x, f(x), f(g(x)), g(f(x)));

Implicit function application also works via indentation, where commas before newlines are optional.

console.log
  "Hello"
  name
  "!"
  JSON.stringify
    id: getId()
    date: new Date
Edit inline or edit in the Playground!
console.log(
  "Hello",
  name,
  "!",
  JSON.stringify({
    id: getId(),
    date: new Date(),
  }),
);

function

function abort
  process.exit 1
Edit inline or edit in the Playground!
function abort() {
  return process.exit(1);
}
function circle(degrees: number): {x: number, y: number}
  radians := degrees * Math.PI / 180
  x: Math.cos radians
  y: Math.sin radians
Edit inline or edit in the Playground!
function circle(degrees: number): {
  x: number;
  y: number;
} {
  const radians = (degrees * Math.PI) / 180;
  return {
    x: Math.cos(radians),
    y: Math.sin(radians),
  };
}

INFO

Implicit return of the last value in a function can be avoided by specifying a void return type (or Promise<void> for async functions), adding a final semicolon or explicit return, or globally using the directive "civet -implicitReturns". Generators also don't implicitly return (use explicit return to return a special final value).

function abort
  process.exit 1;
Edit inline or edit in the Playground!
function abort() {
  process.exit(1);
}
function abort: void
  process.exit 1
Edit inline or edit in the Playground!
function abort(): void {
  process.exit(1);
}
function run(command: string): Promise<void>
  await exec command
Edit inline or edit in the Playground!
async function run(
  command: string,
): Promise<void> {
  await exec(command);
}
function count()
  yield 1
  yield 2
  yield 3
Edit inline or edit in the Playground!
function* count() {
  yield 1;
  yield 2;
  yield 3;
}

One-Line Functions

function do console.log "Anonymous"
function f do console.log "Named"
function f(x) do console.log x
Edit inline or edit in the Playground!
(function () {
  {
    return console.log("Anonymous");
  }
});
function f() {
  {
    return console.log("Named");
  }
}
function f(x) {
  {
    return console.log(x);
  }
}

Function Overloading

function add(a: string, b: string): string
function add(a: number, b: number): number
  a+b
Edit inline or edit in the Playground!
function add(a: string, b: string): string;
function add(a: number, b: number): number {
  return a + b;
}

Arrow Functions

INFO

Unlike ECMAScript, zero-argument arrows do not need a () prefix, but one-argument arrows do need parentheses around the argument.

abort := => process.exit 1
Edit inline or edit in the Playground!
const abort = () => process.exit(1);
createEffect => console.log data()
greet := (name) => console.log "Hello", name
Edit inline or edit in the Playground!
createEffect(() => console.log(data()));
const greet = (name) =>
  console.log("Hello", name);

INFO

=> makes arrow functions as usual, while -> makes functions (which can have this assigned via .call).

add := (a: number, b: number) => a + b
Edit inline or edit in the Playground!
const add = (a: number, b: number) => a + b;
add := (a: number, b: number) -> a + b
Edit inline or edit in the Playground!
const add = function (a: number, b: number) {
  return a + b;
};

INFO

Unlike ECMAScript, even multi-line arrow functions implicitly return their last value. See above for how to avoid this behavior.

circle := (degrees: number): {x: number, y: number} =>
  radians := degrees * Math.PI / 180
  x: Math.cos radians
  y: Math.sin radians
Edit inline or edit in the Playground!
const circle = (
  degrees: number,
): { x: number; y: number } => {
  const radians = (degrees * Math.PI) / 180;
  return {
    x: Math.cos(radians),
    y: Math.sin(radians),
  };
};

You can also use Unicode arrows:

curryAdd := (a: number) → (b: number) ⇒ a + b
Edit inline or edit in the Playground!
const curryAdd = function (a: number) {
  return (b: number) => a + b;
};

return.value

Instead of specifying a function's return value when it returns, you can prepare it ahead of time using return.value (or its shorthand, assigning to return). Using this feature disables implicit return for that function.

function sum(list: number[])
  return .= 0
  for item of list
    return += item
Edit inline or edit in the Playground!
function sum(list: number[]) {
  let ret = 0;
  for (const item of list) {
    ret += item;
  }
  return ret;
}
function search<T>(list: T[]): T | undefined
  return unless list
  for item of list
    if match item
      return = item
  return++ if return.value
  list.destroy()
Edit inline or edit in the Playground!
function search<T>(list: T[]): T | undefined {
  let ret: T | undefined;
  if (!list) {
    return ret;
  }
  for (const item of list) {
    if (match(item)) {
      ret = item;
    }
  }
  if (ret) {
    ret++;
  }
  list.destroy();
  return ret;
}

Single-Argument Function Shorthand (&)

& acts as a placeholder for the argument of a single-argument function, with the function wrapper lifted to just inside the nearest function call, assignment, pipeline, return, or yield.

x.map &.name
x.map &.profile?.name[0...3]
x.map &.callback a, b
x.map +&
x.map typeof &
x.forEach delete &.old
await.allSettled x.map await &.json()
x.map [&, true]
x.map [&, &.toUpperCase()]
x.map name: &
x.filter &
x.filter 0 <= & < n
x.map (& + 1) % n
capitalize := &[0].toUpperCase() +
              &[1..].toLowerCase()
Edit inline or edit in the Playground!
x.map(($) => $.name);
x.map(($1) => $1.profile?.name.slice(0, 3));
x.map(($2) => $2.callback(a, b));
x.map(($3) => +$3);
x.map(($4) => typeof $4);
x.forEach(($5) => delete $5.old);
await Promise.allSettled(
  x.map(async ($6) => await $6.json()),
);
x.map(($7) => [$7, true]);
x.map(($8) => [$8, $8.toUpperCase()]);
x.map(($9) => ({ name: $9 }));
x.filter(($10) => $10);
x.filter(($11) => 0 <= $11 && $11 < n);
x.map(($12) => ($12 + 1) % n);
const capitalize = ($13) =>
  $13[0].toUpperCase() +
  $13.slice(1).toLowerCase();

INFO

Short function block syntax originally inspired by Ruby symbol to proc, Crystal, or Elm record access.

You can also omit & when starting with a . or ?. property access:

x.map .name
x.map ?.profile?.name[0...3]
x.map `${.firstName} ${.lastName}`
Edit inline or edit in the Playground!
x.map(($) => $.name);
x.map(($1) => $1?.profile?.name.slice(0, 3));
x.map(($2) => `${$2.firstName} ${$2.lastName}`);

You can also assign properties:

x.map .name = "Civet" + i++
Edit inline or edit in the Playground!
x.map(($) => ($.name = "Civet" + i++));

You can also type the argument (but if you use type operators, the type needs to be wrapped in parentheses):

increment := &: number + 1
show := &: ??? |> JSON.stringify |> console.log
identity := &?: (number | string)
Edit inline or edit in the Playground!
const increment = ($: number) => $ + 1;
const show = ($1: unknown) =>
  console.log(JSON.stringify($1));
const identity = ($2?: number | string) => $2;

INFO

Note that & is the identity function while (&) is a bitwise AND function. Prior to Civet 0.7.0, (&) was the identity function and & was invalid.

Partial Function Application

Another shorthand for one-argument functions is to call a function with a . placeholder argument:

console.log "result:", .
Edit inline or edit in the Playground!
($) => console.log("result:", $);

More generally, if you use . within a function call, that call gets wrapped in a one-argument function and . gets replaced by that argument. The wrapper also lifts above unary operations (including await) and throw. You can use . multiple times in the same function:

compute ., . + 1, . * 2, (.).toString()
Edit inline or edit in the Playground!
($) => compute($, $ + 1, $ * 2, $.toString());

A key difference between & and . is that . lifts beyond a function call (and requires one), while & does not:

f a, &
f a, .
Edit inline or edit in the Playground!
f(a, ($) => $);
($1) => f(a, $1);

Binary Operators as Functions

Wrapping a binary operator in parentheses turns it into a two-argument function:

numbers.reduce (+)
booleans.reduce (||), false
masks.reduce (&), 0xfff
Edit inline or edit in the Playground!
numbers.reduce((a, b) => a + b);
booleans.reduce((a1, b1) => a1 || b1, false);
masks.reduce((a2, b2) => a2 & b2, 0xfff);

Binary Operator Sections

Like Haskell, you can specify one of the arguments in a parenthesized binary operator to make a one-argument function instead:

counts.map (1+)
.map (2*)
.map (**2)
Edit inline or edit in the Playground!
counts
  .map((b) => 1 + b)
  .map((b1) => 2 * b1)
  .map((a) => a ** 2);

Note that + and - get treated as unary operators first. Add a space after them to make them binary operator sections.

(+x)
(+ x)
Edit inline or edit in the Playground!
+x;
(a) => a + x;

The provided left- or right-hand side can include more binary operators:

counts.map (* 2 + 1)
Edit inline or edit in the Playground!
counts.map((a) => a * 2 + 1);

You can also build functions using assignment operators on the right:

new Promise (resolve =)
callback := (sum +=)
Edit inline or edit in the Playground!
new Promise((b) => (resolve = b));
const callback = (b1) => (sum += b1);

You can also make sections from the pattern matching operator is like:

array.filter (is like {type, children})
Edit inline or edit in the Playground!
array.filter(
  (a) =>
    typeof a === "object" &&
    a != null &&
    "type" in a &&
    "children" in a,
);

Functions as Infix Operators

You can "bless" an existing function to behave as an infix operator (and a negated form) like so:

operator contains
x contains y
x not contains y
x !contains y
Edit inline or edit in the Playground!
contains(x, y);
!contains(x, y);
!contains(x, y);

You can combine this with a variable declaration:

operator {min, max} := Math
value min ceiling max floor
Edit inline or edit in the Playground!
const { min, max } = Math;
max(min(value, ceiling), floor);

You can also define an operator with a function body:

operator calls<T,R>(t: T, f: (this: T) => R): R
  f.call(t)
this calls callback
Edit inline or edit in the Playground!
function calls<T, R>(t: T, f: (this: T) => R): R {
  return f.call(t);
}
calls(this, callback);

Operators are just functions in the end, and behave as such when used unambiguously:

operator foo
x foo foo(y, z)
x (foo) y
Edit inline or edit in the Playground!
foo(x, foo(y, z));
x(foo(y));

You can also import functions from another module as operators (independent of whether they are declared as operators in the other module):

import { operator contains } from 'bar'
x contains y
export operator has(x, y)
  y contains x
Edit inline or edit in the Playground!
import { contains } from "bar";
contains(x, y);
export function has(x, y) {
  return contains(y, x);
}
import operator { plus, minus } from ops
a plus b minus c
Edit inline or edit in the Playground!
import { plus, minus } from "ops";
minus(plus(a, b), c);

By default, custom infix operators have a precedence between relational and arithmetic operators, and are left-associative:

operator foo
a < b + c foo d * e foo f
Edit inline or edit in the Playground!
a < foo(foo(b + c, d * e), f);

You can specify a custom precedence with looser/tighter/same followed by another operator (with symbols wrapped in parentheses). This specification goes after the operator name or before a declaration.

operator dot looser (*) (p, q)
  p.x * q.x + p.y * q.y
operator looser dot DOT := dot
a + b * c dot d * e DOT f * g dot h * i + j
Edit inline or edit in the Playground!
function dot(p, q) {
  return p.x * q.x + p.y * q.y;
}
const DOT = dot;
a + DOT(dot(b * c, d * e), dot(f * g, h * i)) + j;
operator { plus same (+), times same (*) }
  from ops
a times b plus c times d
Edit inline or edit in the Playground!
import { plus, times } from "ops";
plus(times(a, b), times(c, d));

You can specify a custom associativity with left/right/non/relational/arguments:

operator x left  // left is default
a x b x c
operator y right
a y b y c
operator z non
a z b
// a z b z c is an error
operator cmp relational
a < b cmp c instanceof d
operator combine arguments
a combine b combine c
Edit inline or edit in the Playground!
// left is default
x(x(a, b), c);

y(a, y(b, c));

z(a, b);
// a z b z c is an error

a < b && cmp(b, c) && c instanceof d;

combine(a, b, c);

Operator Assignment

Even without blessing a function as an operator, you can use it in an assignment form:

{min, max} := Math
smallest .= Infinity
largest .= -Infinity
for item in items
  smallest min= item
  largest max= item
Edit inline or edit in the Playground!
const { min, max } = Math;
let smallest = Infinity;
let largest = -Infinity;
for (const item in items) {
  smallest = min(smallest, item);
  largest = max(largest, item);
}

Conditions

If/Else

if coffee or relaxed
  code()
else
  sleep()
Edit inline or edit in the Playground!
if (coffee || relaxed) {
  code();
} else {
  sleep();
}

The condition can also be indented:

if
  (or)
    coffee
    relaxed
  code()
else
  sleep()
Edit inline or edit in the Playground!
if (coffee || relaxed) {
  code();
} else {
  sleep();
}

If/Then/Else

For complex conditions that involve indented function application, you can add an explicit then:

if (or)
  coffee
  relaxed
then
  code()
else
  sleep()
Edit inline or edit in the Playground!
if (coffee || relaxed) {
  code();
} else {
  sleep();
}

One-Line If/Then/Else

if coffee or relaxed then code() else sleep()
Edit inline or edit in the Playground!
if (coffee || relaxed) code();
else sleep();

If/Else Expressions

name :=
  if power === Infinity
    "Saitama"
  else if power > 9000
    "Goku"
caps := if name? then name.toUpperCase() else 'ANON'
Edit inline or edit in the Playground!
let ref;
if (power === Infinity) {
  ref = "Saitama";
} else if (power > 9000) {
  ref = "Goku";
} else {
  ref = void 0;
}
const name = ref;
let ref1;
if (name != null) ref1 = name.toUpperCase();
else ref1 = "ANON";
const caps = ref1;

Unless

unless tired
  code()
Edit inline or edit in the Playground!
if (!tired) {
  code();
}

Postfix If/Unless

civet.speed = 15 if civet.rested
Edit inline or edit in the Playground!
if (civet.rested) {
  civet.speed = 15;
}

For complex conditions that involve indented function application, the condition can be indented:

sleep() unless
  (or)
    coffee
    relaxed
Edit inline or edit in the Playground!
if (!(coffee || relaxed)) {
  sleep();
}

Switch

switch can be used in three different forms: case, when, and pattern matching. You can mix case and when, but not the other types.

Case

Similar to JavaScript, but with optional punctuation and multiple matches in one line:

switch dir
  case 'N'
  case 'S'
    console.log 'vertical'
    break
  case 'E', 'W'
    console.log 'horizontal'
  default
    console.log 'horizontal or unknown'
Edit inline or edit in the Playground!
switch (dir) {
  case "N":
  case "S":
    console.log("vertical");
    break;
  case "E":
  case "W":
    console.log("horizontal");
  default:
    console.log("horizontal or unknown");
}

When

when is a nicer version of case, with an automatic break at the end (cancelable with continue switch) and a braced block to keep variable declarations local to the branch:

switch dir
  when 'N', 'S'
    console.log 'vertical'
  when 'E', 'W'
    console.log 'horizontal'
    continue switch
  else
    console.log 'horizontal or unknown'
Edit inline or edit in the Playground!
switch (dir) {
  case "N":
  case "S": {
    console.log("vertical");
    break;
  }
  case "E":
  case "W": {
    console.log("horizontal");
  }
  default: {
    console.log("horizontal or unknown");
  }
}

An example with implicit return and same-line values using then:

getX := (civet: Civet, dir: Dir) =>
  switch dir
    when '>' then civet.x + 3
    when '<' then civet.x - 1
    when '^' then civet.x + 0.3
Edit inline or edit in the Playground!
const getX = (civet: Civet, dir: Dir) => {
  switch (dir) {
    case ">": {
      return civet.x + 3;
    }
    case "<": {
      return civet.x - 1;
    }
    case "^": {
      return civet.x + 0.3;
    }
  }
};

Pattern Matching

switch s
  ""
    console.log "nothing"
  /^\s+$/
    console.log "whitespace"
  "hi"
    console.log "greeting"
Edit inline or edit in the Playground!
if (s === "") {
  console.log("nothing");
} else if (
  typeof s === "string" &&
  /^\s+$/.test(s)
) {
  console.log("whitespace");
} else if (s === "hi") {
  console.log("greeting");
}
switch a
  []
    console.log "empty"
  [item]
    console.log "one", item
  [first, ...middle, last]
    console.log "multiple", first, "...", last
  else
    console.log "not array"
Edit inline or edit in the Playground!
function len<
  T extends readonly unknown[],
  N extends number,
>(arr: T, length: N): arr is T & { length: N } {
  return arr.length === length;
}
if (Array.isArray(a) && len(a, 0)) {
  console.log("empty");
} else if (Array.isArray(a) && len(a, 1)) {
  const [item] = a;
  console.log("one", item);
} else if (Array.isArray(a) && a.length >= 2) {
  const [first, ...middle] = a,
    [last] = middle.splice(-1);
  console.log("multiple", first, "...", last);
} else {
  console.log("not array");
}

INFO

Array patterns are exact; object patterns allow unmatched properties (similar to TypeScript types).

switch x
  {type: "text", content}
    console.log `"${content}"`
  {type, ...rest}
    console.log `unknown type ${type}`
  else
    console.log 'unknown'
Edit inline or edit in the Playground!
if (
  typeof x === "object" &&
  x != null &&
  "type" in x &&
  x.type === "text" &&
  "content" in x
) {
  const { type, content } = x;
  console.log(`"${content}"`);
} else if (
  typeof x === "object" &&
  x != null &&
  "type" in x
) {
  const { type, ...rest } = x;
  console.log(`unknown type ${type}`);
} else {
  console.log("unknown");
}
switch x
  [{type: "text", content: /^\s+$/}, ...rest]
    console.log "leading whitespace"
  [{type: "text", content}, ...rest]
    console.log "leading text:", content
  [{type}, ...rest]
    console.log "leading type:", type
Edit inline or edit in the Playground!
if (
  Array.isArray(x) &&
  x.length >= 1 &&
  typeof x[0] === "object" &&
  x[0] != null &&
  "type" in x[0] &&
  x[0].type === "text" &&
  "content" in x[0] &&
  typeof x[0].content === "string" &&
  /^\s+$/.test(x[0].content)
) {
  const [{ type, content }, ...rest] = x;
  console.log("leading whitespace");
} else if (
  Array.isArray(x) &&
  x.length >= 1 &&
  typeof x[0] === "object" &&
  x[0] != null &&
  "type" in x[0] &&
  x[0].type === "text" &&
  "content" in x[0]
) {
  const [{ type, content }, ...rest] = x;
  console.log("leading text:", content);
} else if (
  Array.isArray(x) &&
  x.length >= 1 &&
  typeof x[0] === "object" &&
  x[0] != null &&
  "type" in x[0]
) {
  const [{ type }, ...rest] = x;
  console.log("leading type:", type);
}

INFO

You can also use condition fragments as patterns.

switch x
  < 0
    console.log "it's negative"
  > 0
    console.log "it's positive"
  is 0
    console.log "it's zero"
  else
    console.log "it's something else"
Edit inline or edit in the Playground!
if (x < 0) {
  console.log("it's negative");
} else if (x > 0) {
  console.log("it's positive");
} else if (x === 0) {
  console.log("it's zero");
} else {
  console.log("it's something else");
}
switch x
  % 15 is 0
    console.log "fizzbuzz"
  % 3 is 0
    console.log "fizz"
  % 5 is 0
    console.log "buzz"
  else
    console.log x
Edit inline or edit in the Playground!
if (x % 15 === 0) {
  console.log("fizzbuzz");
} else if (x % 3 === 0) {
  console.log("fizz");
} else if (x % 5 === 0) {
  console.log("buzz");
} else {
  console.log(x);
}

INFO

Aliasing object properties works the same as destructuring.

switch e
  {type, key: eventKey}
    return [type, eventKey]
Edit inline or edit in the Playground!
if (
  typeof e === "object" &&
  e != null &&
  "type" in e &&
  "key" in e
) {
  const { type, key: eventKey } = e;
  return [type, eventKey];
}

INFO

Patterns can aggregate duplicate bindings.

switch x
  [{type}, {type}]
    type
Edit inline or edit in the Playground!
function len<
  T extends readonly unknown[],
  N extends number,
>(arr: T, length: N): arr is T & { length: N } {
  return arr.length === length;
}
if (
  Array.isArray(x) &&
  len(x, 2) &&
  typeof x[0] === "object" &&
  x[0] != null &&
  "type" in x[0] &&
  typeof x[1] === "object" &&
  x[1] != null &&
  "type" in x[1]
) {
  const [{ type: type1 }, { type: type2 }] = x;
  const type = [type1, type2];
  type;
}

Use ^x to refer to variable x in the parent scope, as opposed to a generic name that gets destructured. (This is called "pinning" in Elixir and Erlang.)

switch x
  ^y
    console.log "y"
  [^y]
    console.log "array with y"
  [y]
    console.log "array with", y
  ^getSpecial()
    console.log "special"
Edit inline or edit in the Playground!
function len<
  T extends readonly unknown[],
  N extends number,
>(arr: T, length: N): arr is T & { length: N } {
  return arr.length === length;
}
if (x === y) {
  console.log("y");
} else if (
  Array.isArray(x) &&
  len(x, 1) &&
  x[0] === y
) {
  const [] = x;
  console.log("array with y");
} else if (Array.isArray(x) && len(x, 1)) {
  const [y] = x;
  console.log("array with", y);
} else if (x === getSpecial()) {
  console.log("special");
}

You can also write general expressions after ^. Member expressions like enum values do not need ^:

function directionVector(dir: Direction)
  switch dir
    Direction.Left
      [-1, 0]
    Direction.Right
      [+1, 0]
    Direction.Down
      [0, -1]
    ^Direction.Up
      [0, +1]
Edit inline or edit in the Playground!
function directionVector(dir: Direction) {
  if (dir === Direction.Left) {
    return [-1, 0];
  } else if (dir === Direction.Right) {
    return [+1, 0];
  } else if (dir === Direction.Down) {
    return [0, -1];
  } else if (dir === Direction.Up) {
    return [0, +1];
  }
  return;
}

If you just want to match a value against a single pattern, you can use a declaration in a condition:

if [{type, content}, ...rest] := x
  console.log "leading content", content
Edit inline or edit in the Playground!
if (
  x &&
  Array.isArray(x) &&
  x.length >= 1 &&
  typeof x[0] === "object" &&
  x[0] != null &&
  "type" in x[0] &&
  "content" in x[0]
) {
  const [{ type, content }, ...rest] = x;
  console.log("leading content", content);
}

If you just want to check whether a value matches a single pattern, you can use the is like or is not like operator:

if x is like [{type, content: /^\s+$/}, ...]
  console.log "leading whitespace"
Edit inline or edit in the Playground!
if (
  Array.isArray(x) &&
  x.length >= 1 &&
  typeof x[0] === "object" &&
  x[0] != null &&
  "type" in x[0] &&
  "content" in x[0] &&
  typeof x[0].content === "string" &&
  /^\s+$/.test(x[0].content)
) {
  console.log("leading whitespace");
}

In particular, this gives a nice shorthand for RegExp.prototype.test:

isInt := x is like /^[+-]?\d+$/
exists := x? is not like /^\s*$/
Edit inline or edit in the Playground!
const isInt =
  typeof x === "string" && /^[+-]?\d+$/.test(x);
const exists =
  x != null &&
  !(typeof x === "string" && /^\s*$/.test(x));

Loops

All JavaScript loops are available, with optional parentheses around the clause.

for let i = 0; i < 100; i++
  console.log i
Edit inline or edit in the Playground!
for (let i = 0; i < 100; i++) {
  console.log(i);
}

for..of

Looping over an iterator via for..of defaults to const:

for item of list
  console.log item
Edit inline or edit in the Playground!
for (const item of list) {
  console.log(item);
}
for let item of list
  item *= item
  console.log item
Edit inline or edit in the Playground!
for (let item of list) {
  item *= item;
  console.log(item);
}
for var item of list
  console.log item
console.log "Last item:", item
Edit inline or edit in the Playground!
for (var item of list) {
  console.log(item);
}
console.log("Last item:", item);

You can also keep track of the current index of the iteration by specifying a comma and a second variable (also defaulting to const):

for item, index of list
  console.log `${index}th item is ${item}`
Edit inline or edit in the Playground!
let i = 0;
for (const item of list) {
  const index = i++;
  console.log(`${index}th item is ${item}`);
}

You can add types to the declarations (unlike TypeScript):

for var item: Item? of list
  console.log item
if item?
  console.log "Last item:", item
else
  console.log "No items"
Edit inline or edit in the Playground!
for (const item1 of list) {
  var item: Item | undefined = item1;
  console.log(item);
}
if (item != null) {
  console.log("Last item:", item);
} else {
  console.log("No items");
}

for each..of

For Arrays and other objects implementing .length and [i] indexing, you can use for each..of as an optimized form of for..of (without building an iterator):

for each item of list
  console.log item
Edit inline or edit in the Playground!
for (let i = 0, len = list.length; i < len; i++) {
  const item = list[i];
  console.log(item);
}
for each let item of list
  item *= item
  console.log item
Edit inline or edit in the Playground!
for (let i = 0, len = list.length; i < len; i++) {
  let item = list[i];
  item *= item;
  console.log(item);
}
for each item, index of list
  console.log `${index}th item is ${item}`
Edit inline or edit in the Playground!
for (let i = 0, len = list.length; i < len; i++) {
  const index = i;
  const item = list[i];
  console.log(`${index}th item is ${item}`);
}

for each loops are similar to Array.prototype.forEach (hence the name), but are more efficient and allow for e.g. break and continue.

for..in

Looping over properties of an object via for..in defaults to const:

for key in object
  console.log key
Edit inline or edit in the Playground!
for (const key in object) {
  console.log(key);
}
for var key in object
  console.log key
console.log `Last key is ${key}`
Edit inline or edit in the Playground!
for (var key in object) {
  console.log(key);
}
console.log(`Last key is ${key}`);

You can also retrieve the corresponding value by specifying a comma and a second variable (also defaulting to const):

for key, value in object
  console.log `${key} maps to ${value}`
Edit inline or edit in the Playground!
for (const key in object) {
  const value = object[key];
  console.log(`${key} maps to ${value}`);
}

If your object might have a prototype with enumerable properties, you can skip them with own:

for own key in object
  console.log key
Edit inline or edit in the Playground!
var hasProp: <T>(
  object: T,
  prop: PropertyKey,
) => boolean = ({}.constructor as any).hasOwn;
for (const key in object) {
  if (!hasProp(object, key)) continue;
  console.log(key);
}

You can add types to the declarations (unlike TypeScript):

for key: keyof typeof object, value in object
  process key, value
Edit inline or edit in the Playground!
for (const key1 in object) {
  const key: keyof typeof object = key1;
  const value = object[key];
  process(key, value);
}

Loop Expressions

If needed, loops automatically assemble an Array of the last value within the body of the loop for each completed iteration.

squares :=
  for item of list
    item * item
Edit inline or edit in the Playground!
const results = [];
for (const item of list) {
  results.push(item * item);
}
const squares = results;
evenSquares :=
  for item of list
    continue unless item % 2 == 0
    item * item
Edit inline or edit in the Playground!
const results = [];
for (const item of list) {
  if (!(item % 2 == 0)) {
    continue;
  }
  results.push(item * item);
}
const evenSquares = results;
function parities(list: number[]): string[]
  for item of list
    if item % 2 === 0
      "even"
    else
      "odd"
Edit inline or edit in the Playground!
function parities(list: number[]): string[] {
  const results = [];
  for (const item of list) {
    if (item % 2 === 0) {
      results.push("even");
    } else {
      results.push("odd");
    }
  }
  return results;
}

INFO

When not at the top level, loop expressions wrap in an IIFE, so you cannot use return inside such a loop, nor can you break or continue any outer loop.

If you don't specify a body, for loops list the item being iterated over:

array := for item of iterable
coordinates := for {x, y} of objects
keys := for key in object
Edit inline or edit in the Playground!
const results = [];
for (const item of iterable) {
  results.push(item);
}
const array = results;
const results1 = [];
for (const { x, y } of objects) {
  results1.push({ x, y });
}
const coordinates = results1;
const results2 = [];
for (const key in object) {
  results2.push(key);
}
const keys = results2;

Loops that use await automatically get awaited. If you'd rather obtain the promise for the results so you can await them yourself, use async for.

results :=
  for url of urls
    await fetch url
Edit inline or edit in the Playground!
const results1 = [];
for (const url of urls) {
  results1.push(await fetch(url));
}
const results = results1;
promise :=
  async for url of urls
    await fetch url
Edit inline or edit in the Playground!
const promise = (async () => {
  const results = [];
  for (const url of urls) {
    results.push(await fetch(url));
  }
  return results;
})();

Generator Expressions

All loops have a starred form that makes a generator function, yielding items one at a time instead of building the entire array at once:

numbers := for* n of [0..]
squares := for* n of numbers
  n * n
Edit inline or edit in the Playground!
const numbers = (function* () {
  for (let i = 0; ; ++i) {
    const n = i;
    yield n;
  }
})();
const squares = (function* () {
  for (const n of numbers) {
    yield n * n;
  }
})();

As statements in a function, the starred forms yield directly, allowing you to do multiple such loops in the same function:

function mapConcatIter(f, a, b)
  for* item of a
    f item
  for* item of b
    f item
Edit inline or edit in the Playground!
function* mapConcatIter(f, a, b) {
  for (const item of a) {
    yield f(item);
  }
  for (const item of b) {
    yield f(item);
  }
}

Postfix Loop

console.log item for item of array
Edit inline or edit in the Playground!
for (const item of array) {
  console.log(item);
}

When Condition

for loops can have a when condition to filter iterations. This makes for a nice one-line form to filter and map an array (similar to Python list comprehensions):

squared := v * v for v of list when v?
// or
squared := for v of list when v? then v * v
Edit inline or edit in the Playground!
const squared = (() => {
  const results = [];
  for (const v of list) {
    if (!(v != null)) continue;
    results.push(v * v);
  }
  return results;
})();
// or
const results1 = [];
for (const v of list) {
  if (!(v != null)) continue;
  results1.push(v * v);
}
const squared = results1;

If you don't specify a loop body, you get a filter:

evens := for v of list when v % 2 === 0
Edit inline or edit in the Playground!
const results = [];
for (const v of list) {
  if (!(v % 2 === 0)) continue;
  results.push(v);
}
const evens = results;

To make a generator instead of an array, use for* instead of for.

Reductions

Instead of accumulating the results in an array, for loops can combine the body values according to one of the following reductions.

for some returns whether any body value is truthy, shortcutting once one is found (like Array.prototype.some). If there is no body, any iteration counts as truthy, so it measures whether the iteration is nonempty.

anyEven := for some item of array
  item % 2 === 0
nonEmpty := for some key in object
Edit inline or edit in the Playground!
let results = false;
for (const item of array) {
  if (item % 2 === 0) {
    results = true;
    break;
  }
}
const anyEven = results;
let results1 = false;
for (const key in object) {
  results1 = true;
  break;
}
const nonEmpty = results1;

for every returns whether every body value is truthy, shortcutting if a falsey value is found (like Array.prototype.every). If there is no body, any iteration counts as falsey, so it measures whether the iteration is empty.

allEven := for every item of array
  item % 2 === 0
Edit inline or edit in the Playground!
let results = true;
for (const item of array) {
  if (!(item % 2 === 0)) {
    results = false;
    break;
  }
}
const allEven = results;
emptyOwn := for every own key in object
Edit inline or edit in the Playground!
var hasProp: <T>(
  object: T,
  prop: PropertyKey,
) => boolean = ({}.constructor as any).hasOwn;
let results = true;
for (const key in object) {
  if (!hasProp(object, key)) continue;
  results = false;
  break;
}
const emptyOwn = results;

for count counts how many body values are truthy. If there is no body, any iteration counts as truthy.

numEven := for count item of array
  item % 2 === 0
numKeys := for count key in object
Edit inline or edit in the Playground!
let results = 0;
for (const item of array) {
  if (item % 2 === 0) ++results;
}
const numEven = results;
let results1 = 0;
for (const key in object) {
  ++results1;
}
const numKeys = results1;

for sum adds up the body values with +, starting from 0. If there is no body, it adds the item being looped over.

sumOfSquares := for sum item of array
  item * item
sum := for sum item of array
Edit inline or edit in the Playground!
let results = 0;
for (const item of array) {
  results += item * item;
}
const sumOfSquares = results;
let results1 = 0;
for (const item of array) {
  results1 += item;
}
const sum = results1;

for product multiples the body values with *. If there is no body, it multiplies the item being looped over.

prod := for product item of array
nonZeroProd := for product item of array when item
Edit inline or edit in the Playground!
let results = 1;
for (const item of array) {
  results *= item;
}
const prod = results;
let results1 = 1;
for (const item of array) {
  if (!item) continue;
  results1 *= item;
}
const nonZeroProd = results1;

for min/max finds the minimum/maximum body value, or +Infinity/-Infinity if there are none. If there is no body, it uses the item being looped over.

min := for min item of array
max := for max item of array
Edit inline or edit in the Playground!
let results = Infinity;
for (const item of array) {
  results = Math.min(results, item);
}
const min = results;
let results1 = -Infinity;
for (const item of array) {
  results1 = Math.max(results1, item);
}
const max = results1;

for join concatenates the body values as strings, using +. It's like for sum but starting from "" instead of 0.

all := for join item of array
  `[${item.type}] ${item.title}`
Edit inline or edit in the Playground!
let results = "";
for (const item of array) {
  results += `[${item.type}] ${item.title}`;
}
const all = results;

Implicit bodies in for sum/product/min/max/join reductions can use a single destructuring:

xMin := for min {x} of points
Edit inline or edit in the Playground!
let results = Infinity;
for (const { x } of points) {
  results = Math.min(results, x);
}
const xMin = results;
xMin := for min [x] of points
yMin := for min [, y] of points
Edit inline or edit in the Playground!
let results = Infinity;
for (const [x] of points) {
  results = Math.min(results, x);
}
const xMin = results;
let results1 = Infinity;
for (const [, y] of points) {
  results1 = Math.min(results1, y);
}
const yMin = results1;

Object Comprehensions

Loops can also accumulate their body values into an object. When a loop is inside a braced object literal, its body value is spread into the containing object.

object  := {a: 1, b: 2, c: 3}
doubled := {
  for key in object
    [key]: 2 * object[key]
}
Edit inline or edit in the Playground!
const object = { a: 1, b: 2, c: 3 };
const doubled = {
  ...(() => {
    const results = {};
    for (const key in object) {
      Object.assign(results, {
        [key]: 2 * object[key],
      });
    }
    return results;
  })(),
};
i .= 1
squares := {
  do
    [i]: i * i
  while i++ < 10
}
Edit inline or edit in the Playground!
let i = 1;
const squares = {
  ...(() => {
    const results = {};
    do {
      Object.assign(results, { [i]: i * i });
    } while (i++ < 10);
    return results;
  })(),
};

Loops can be freely mixed with other object properties.

rateLimits := {
  admin: Infinity
  for user of users
    [user.name]: getRemainingLimit(user)
}
Edit inline or edit in the Playground!
const rateLimits = {
  admin: Infinity,
  ...(() => {
    const results = {};
    for (const user of users) {
      Object.assign(results, {
        [user.name]: getRemainingLimit(user),
      });
    }
    return results;
  })(),
};

Infinite Loop

i .= 0
loop
  i++
  break if i > 5
Edit inline or edit in the Playground!
let i = 0;
while (true) {
  i++;
  if (i > 5) {
    break;
  }
}

Range Loop

for i of [0...array.length]
  array[i] = array[i].toString()
Edit inline or edit in the Playground!
for (
  let end = array.length, i1 = 0;
  i1 < end;
  ++i1
) {
  const i = i1;
  array[i] = array[i].toString();
}
for i of [0...n] by 2
  console.log i, "is even"
Edit inline or edit in the Playground!
for (let i1 = 0; i1 < n; i1 += 2) {
  const i = i1;
  console.log(i, "is even");
}
for [1..5]
  attempt()
Edit inline or edit in the Playground!
for (let i = 1; i <= 5; ++i) {
  attempt();
}
for i of [1..]
  attempt i
Edit inline or edit in the Playground!
for (let i1 = 1; ; ++i1) {
  const i = i1;
  attempt(i);
}

You can control loop direction and include or exclude endpoints using .. with <=/>= or </>:

for i of [first..<=last]
  console.log array[i]
Edit inline or edit in the Playground!
for (let i1 = first; i1 <= last; ++i1) {
  const i = i1;
  console.log(array[i]);
}
for i of [left<..<right]
  console.log array[i]
Edit inline or edit in the Playground!
for (let i1 = left + 1; i1 < right; ++i1) {
  const i = i1;
  console.log(array[i]);
}

See also range literals.

Until Loop

i .= 0
until i > 5
  i++
Edit inline or edit in the Playground!
let i = 0;
while (!(i > 5)) {
  i++;
}

Do...While/Until Loop

total .= 0
item .= head
do
  total += item.value
  item = item.next
while item?
Edit inline or edit in the Playground!
let total = 0;
let item = head;
do {
  total += item.value;
  item = item.next;
} while (item != null);

Labels

:outer while (list = next())?
  for item of list
    if finale item
      break outer
  continue :outer
Edit inline or edit in the Playground!
outer: while ((list = next()) != null) {
  for (const item of list) {
    if (finale(item)) {
      break outer;
    }
  }
  continue outer;
}

INFO

Labels have the colon on the left to avoid conflict with implicit object literals. The colons are optional in break and continue.

Controlling Loop Value

function varVector(items, mean)
  for item of items
    continue with 0 unless item?
    item -= mean
    item * item
Edit inline or edit in the Playground!
function varVector(items, mean) {
  const results = [];
  for (const item of items) {
    if (!(item != null)) {
      results.push(0);
      continue;
    }
    item -= mean;
    results.push(item * item);
  }
  return results;
}
found :=
  loop
    item := nextItem()
    break with item if item.found
    process item
Edit inline or edit in the Playground!
let results;
while (true) {
  const item = nextItem();
  if (item.found) {
    results = item;
    break;
  }
  process(item);
}
const found = results;
function process(lists)
  :outer for list of lists
    for item of list
      if item is "abort"
        break outer with item
      if item is "length"
        continue :outer with list.length
Edit inline or edit in the Playground!
function process(lists) {
  let results;
  results = [];
  outer: for (const list of lists) {
    const results1 = [];
    for (const item of list) {
      if (item === "abort") {
        results = item;
        break outer;
      }
      if (item === "length") {
        results.push(list.length);
        continue outer;
      } else {
        results1.push(void 0);
      }
    }
    results.push(results1);
  }
  return results;
}

Other Blocks

Try Blocks

Like JavaScript, try blocks can have catch and/or finally blocks:

try
  compute()
catch e
  console.error e
finally
  cleanup()
Edit inline or edit in the Playground!
try {
  compute();
} catch (e) {
  console.error(e);
} finally {
  cleanup();
}

Unlike JavaScript, you can omit both catch and finally for a default behavior of "ignore all exceptions":

try
  compute()
Edit inline or edit in the Playground!
try {
  compute();
} catch (e) {}

In addition, you can add an else block between (optional) catch and (optional) finally, which executes whenever the catch block does not:

try
  result = compute()
catch e
  callback "error", e
else
  // exceptions here will not trigger catch block
  callback result
Edit inline or edit in the Playground!
let ok = true;
try {
  result = compute();
} catch (e) {
  ok = false;
  callback("error", e);
} finally {
  if (ok) {
    // exceptions here will not trigger catch block
    callback(result);
  }
}

You can also specify multiple catch blocks using pattern matching:

try
  foo()
catch <? RangeError, <? ReferenceError
  console.log "R...Error"
catch {message: /bad/}
  console.log "bad"
catch e
  console.log "other", e
Edit inline or edit in the Playground!
try {
  foo();
} catch (e1) {
  if (
    e1 instanceof RangeError ||
    e1 instanceof ReferenceError
  ) {
    console.log("R...Error");
  } else if (
    typeof e1 === "object" &&
    e1 != null &&
    "message" in e1 &&
    typeof e1.message === "string" &&
    /bad/.test(e1.message)
  ) {
    const { message } = e1;
    console.log("bad");
  } else {
    let e = e1;
    console.log("other", e);
  }
}

If you omit a catch-all at the end, the default behavior is to re-throw the error:

try
  foo()
catch {message: /^EPIPE:/}
Edit inline or edit in the Playground!
try {
  foo();
} catch (e) {
  if (
    typeof e === "object" &&
    e != null &&
    "message" in e &&
    typeof e.message === "string" &&
    /^EPIPE:/.test(e.message)
  ) {
    const { message } = e;
  } else {
    throw e;
  }
}

Finally, you can specify a finally block without a try block, and it automatically wraps the rest of the block (similar to defer in Zig and Swift):

depth .= 0
function recurse(node)
  depth++
  finally depth--
  console.log depth, "entering", node
  finally console.log depth, "exiting", node
  return unless node?
  recurse child for child of node
Edit inline or edit in the Playground!
let depth = 0;
function recurse(node) {
  depth++;
  try {
    console.log(depth, "entering", node);
    try {
      if (!(node != null)) {
        return;
      }
      const results = [];
      for (const child of node) {
        results.push(recurse(child));
      }
      return results;
    } finally {
      console.log(depth, "exiting", node);
    }
  } finally {
    depth--;
  }
}

Do Blocks

To put multiple lines in a scope and possibly an expression, you can use do without a while/until suffix, similar to TC39 proposal: do expressions.

x := 5
do
  x := 10
  console.log x
console.log x
Edit inline or edit in the Playground!
const x = 5;
{
  const x = 10;
  console.log(x);
}
console.log(x);
x := do
  y := f()
  y*y
Edit inline or edit in the Playground!
let ref;
{
  const y = f();
  ref = y * y;
}
const x = ref;

INFO

When not at the top level, do expressions wrap in an IIFE, so you cannot use return, break, or continue within them.

Async Do Blocks

You can create a promise using await notation with async do:

promise := async do
  result := await fetch url
  await result.json()
Edit inline or edit in the Playground!
const promise = (async () => {
  {
    const result = await fetch(url);
    return await result.json();
  }
})();