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
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
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
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
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
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]
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
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"
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
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
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
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})`
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'
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}
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 };

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"
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
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
}
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
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
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
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`
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".#
array.length;
array.length;
"a string also".length;

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

class List
  push(item)
    @[#] = item
  wrap(index)
    @[index %% #]
var modulo: (a: number, b: number) => number = (
  a,
  b,
) => ((a % b) + b) % b;
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
"length" in x;

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

array := {0: 'a', #: 1}
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()
getItems(url)
  .filter(($) => $.match)
  .sort();
document
  ?.querySelectorAll pattern
  .forEach (element) =>
    element.style.color = 'purple'
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
]
const rotate = [c, -s, s, c];
func.apply @, [
  arg1
  arg2
]
func.apply(this, [arg1, arg2]);
people := [
  name: "Alice"
  id: 7
,
  name: "Bob"
  id: 9
]
const people = [
  { name: "Alice", id: 7 },
  { name: "Bob", id: 9 },
];

Bulleted

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

rotate :=
  . c, -s
  . s, c
const rotate = [c, -s, s, c];
func.apply @,
  • arg1
  • arg2
func.apply(this, [arg1, arg2]);
people :=
  . name: "Alice"
    id: 7
  . name: "Bob"
    id: 9
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"
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]
([...head] = [1, 2, 3, 4, 5]),
  ([last] = head.splice(-1));
{a, ...rest, b} = {a: 7, b: 8, x: 0, y: 1}
({ a, b, ...rest } = { a: 7, b: 8, x: 0, y: 1 });
function justDoIt(a, ...args, cb) {
  cb.apply(a, args)
}
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
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.

letters := ['a'..'f']
numbers := [1..10]
reversed := [10..1]
indices := [0...array.length]
const letters = ["a", "b", "c", "d", "e", "f"];
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const reversed = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const indices = ((s, e) => {
  let step = e > s ? 1 : -1;
  return Array.from(
    { length: Math.abs(e - s) },
    (_, i) => s + i * step,
  );
})(0, array.length);

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

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] = []
const start = numbers.slice(0, 1 + 2 || 1 / 0);
const mid = numbers.slice(3, -2);
const end = numbers.slice(-2);
numbers.splice(1, -1 - 1, ...[]);

Strings

Strings can span multiple lines:

console.log "Hello,
world!"
console.log("Hello,\nworld!");

Triple-Quoted Strings

Leading indentation is removed.

console.log '''
  <div>
    Civet
  </div>
'''
console.log(`<div>
  Civet
</div>`);
console.log """
  <div>
    Civet #{version}
  </div>
"""
console.log(`<div>
  Civet ${version}
</div>`);
console.log ```
  <div>
    Civet ${version}
  </div>
```
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
  $
///
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.

Operators

All JavaScript/TypeScript Operators

center := min + length / 2
name := user?.name ?? defaultName
typeof x === "string" && x += "!"
result! as string | number
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
a + (b = c);

Multi Assignment

(count[key] ?= 0)++
(count[key] ?= 0) += 1
++count *= 2
(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?
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
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
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
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"
a &&= b;
a ||= b;
a ??= b;
obj.key ??= "civet";

Optional Chaining

obj?prop
obj?[key]
fun?(arg)
obj?.prop;
obj?.[key];
fun?.(arg);

Optional Chain Assignment

obj?prop = value
obj?[key] = value
fun?(arg).prop = value
fun?(arg)?prop?[key] = value
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?
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
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
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
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"
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
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
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
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
var modulo: (a: number, b: number) => number = (
  a,
  b,
) => ((a % b) + b) % b;
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
var div: (a: number, b: number) => number = (
  a,
  b,
) => Math.floor(a / b);
var modulo: (a: number, b: number) => number = (
  a,
  b,
) => ((a % b) + b) % b;
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
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
console.log(Object.keys(data));

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

x.length |> & + 1 |> .toString()
(x.length + 1).toString();
x.length |> (+ 1) |> .toString()
(x.length + 1).toString();

Build functions by starting with &:

& |> .toString |> console.log
($) => console.log($.toString);
&: number |> (+ 1) |> (* 2) |> Math.round
($: number) => Math.round(($ + 1) * 2);

Use as T to cast types in your pipeline:

data |> JSON.parse |> as MyRecord |> addRecord
addRecord(JSON.parse(data) as MyRecord);

Use await, yield, or return in your pipeline:

fetch url |> await
|> .json() |> await
|> return
return await(await fetch(url)).json();

Pipe assignment:

data |>= .content
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()
array.pop(),
  array.push(5),
  array.sort(),
  array.reverse(),
  array;
count |> & + 1
||> console.log
|> & * 2
||> console.log
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
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'
let ref;
((ref = document.createElement("div")).className =
  "civet"),
  ref.appendChild(
    document.createTextNode("Civet"),
  ),
  ref;

Unicode forms:

data ▷= func1 |▷ func2 ▷ func3
let ref;
(data = func2((ref = func1(data)))), func3(ref);

Await Operators

TC39 proposal: await operations

await.allSettled promises
await Promise.allSettled(promises);
await.all
  for url of urls
    fetch url
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
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()
// Sequential
[await first(), await second()];
// Parallel
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
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
console.log(
  "Hello",
  name,
  "!",
  JSON.stringify({
    id: getId(),
    date: new Date(),
  }),
);

function

function abort
  process.exit 1
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
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;
function abort() {
  process.exit(1);
}
function abort: void
  process.exit 1
function abort(): void {
  process.exit(1);
}
function run(command: string): Promise<void>
  await exec command
async function run(
  command: string,
): Promise<void> {
  await exec(command);
}
function count()
  yield 1
  yield 2
  yield 3
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
(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
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
const abort = () => process.exit(1);
createEffect => console.log data()
greet := (name) => console.log "Hello", name
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
const add = (a: number, b: number) => a + b;
add := (a: number, b: number) -> a + b
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
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
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
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()
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()
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}`
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++
x.map(($) => ($.name = "Civet" + i++));

You can also type the argument:

increment := &: number + 1
show := &: ??? |> JSON.stringify |> console.log
const increment = ($: number) => $ + 1;
const show = ($1: unknown) =>
  console.log(JSON.stringify($1));

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:", .
($) => 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. You can use . multiple times in the same function:

compute ., . + 1, . * 2, (.).toString()
($) => 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, .
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
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)
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)
+x;
(a) => a + x;

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

counts.map (* 2 + 1)
counts.map((a) => a * 2 + 1);

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

new Promise (resolve =)
callback := (sum +=)
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})
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
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
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
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
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
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
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
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
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
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
// 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
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()
if (coffee || relaxed) {
  code();
} else {
  sleep();
}

The condition can also be indented:

if
  (or)
    coffee
    relaxed
  code()
else
  sleep()
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()
if (coffee || relaxed) {
  code();
} else {
  sleep();
}

One-Line If/Then/Else

if coffee or relaxed then code() else sleep()
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'
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()
if (!tired) {
  code();
}

Postfix If/Unless

civet.speed = 15 if civet.rested
if (civet.rested) {
  civet.speed = 15;
}

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

sleep() unless
  (or)
    coffee
    relaxed
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'
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'
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
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"
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"
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'
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
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"
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
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]
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
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"
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]
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
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"
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*$/
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
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
for (const item of list) {
  console.log(item);
}
for let item of list
  item *= item
  console.log item
for (let item of list) {
  item *= item;
  console.log(item);
}
for var item of list
  console.log item
console.log "Last item:", item
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}`
let i = 0;
for (const item of list) {
  const index = i++;
  console.log(`${index}th item is ${item}`);
}

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
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
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}`
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
for (const key in object) {
  console.log(key);
}
for var key in object
  console.log key
console.log `Last key is ${key}`
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}`
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
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);
}

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
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
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"
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.

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
const results1 = [];
for (const url of urls) {
  results1.push(await fetch(url));
}
const results = results1;
promise :=
  async for url of urls
    await fetch url
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:

function mapIter(f, list)
  for* item of list
    f item
function mapIter(f, list) {
  return (function* () {
    for (const item of list) {
      yield f(item);
    }
    return;
  })();
}

Postfix Loop

console.log item for item of array
for (const item of array) {
  console.log(item);
}

Infinite Loop

i .= 0
loop
  i++
  break if i > 5
let i = 0;
while (true) {
  i++;
  if (i > 5) {
    break;
  }
}

Range Loop

for i of [0...array.length]
  array[i] = array[i].toString()
for (
  let end = array.length, i1 = 0, asc = 0 <= end;
  asc ? i1 < end : i1 > end;
  asc ? ++i1 : --i1
) {
  const i = i1;
  array[i] = array[i].toString();
}
for [1..5]
  attempt()
for (let i = 1; i <= 5; ++i) {
  attempt();
}
for i of [1..]
  attempt i
for (let i1 = 1; ; ++i1) {
  const i = i1;
  attempt(i);
}

Until Loop

i .= 0
until i > 5
  i++
let i = 0;
while (!(i > 5)) {
  i++;
}

Do...While/Until Loop

total .= 0
item .= head
do
  total += item.value
  item = item.next
while item?
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
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
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
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
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()
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()
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
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);
  }
}

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
const x = 5;
{
  const x = 10;
  console.log(x);
}
console.log(x);
x := do
  y := f()
  y*y
let ref;
{
  const y = f();
  ref = y * y;
}
const x = ref;

INFO

When not at the top level, do expressions wrap in an IIFE, 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()
const promise = (async () => {
  {
    const result = await fetch(url);
    return await result.json();
  }
})();
await Promise.allSettled for url of urls
  async do
    result := await fetch url
    await result.json()
await Promise.allSettled(
  (() => {
    const results = [];
    for (const url of urls) {
      results.push(
        (async () => {
          {
            const result = await fetch(url);
            return await result.json();
          }
        })(),
      );
    }
    return results;
  })(),
);

Generator Do Blocks

You can create a generator using yield notation with do*:

neighbors := do*
  yield [x-1, y]
  yield [x+1, y]
  yield [x, y-1]
  yield [x, y+1]
const neighbors = (function* () {
  {
    yield [x - 1, y];
    yield [x + 1, y];
    yield [x, y - 1];
    yield [x, y + 1];
  }
})();

Comptime Blocks

comptime blocks are similar to do blocks, but they execute at Civet compilation time. The result of executing the block gets embedded into the output JavaScript code.

value := comptime 1+2+3
const value = 6;
console.log "3rd triangular number is", comptime
  function triangle(n) do n and n + triangle n-1
  triangle 3
console.log("3rd triangular number is", 6);

Note that comptime blocks are executed as separate scripts (separate NodeJS contexts, or top-level eval on the browser), so they have no access to variables in outer scopes. You can use require to load other modules (or import on very recent NodeJS versions, but this generates a warning). The block can be async e.g. via use of await, and the resulting Promise will be awaited during compilation. For serialization into JavaScript code, the result must consist of built-in JavaScript types, including numbers, BigInt, strings, Buffer, URL, RegExp, Date, Array, TypedArray, Set, Map, objects (including getters, setters, property descriptors, Object.create(null), Object.preventExtensions, Object.freeze, Object.seal, but no prototypes), non-built-in functions, classes, and symbols that are properties of Symbol. Functions cannot refer to variables/functions in an outer scope other than global. And there cannot be reference loops. Some of these restrictions may be lifted in the future.

INFO

Inspired by Rust crates comptime and constime, which are a simplified version of Zig's comptime; and other similar compile-time features (sometimes called "macros") such as C++'s constexpr.

Because comptime enables execution of arbitrary code during compilation, it is not enabled by default, nor can it be enabled via a directive or config file. In particular, the VSCode language server will not execute comptime blocks. You can enable comptime evaluation in the CLI using civet --comptime, and in the unplugin using the comptime: true option. If not enabled, comptime blocks will execute at runtime.

// comptime is disabled here
value := comptime 1+2+3
// comptime is disabled here
const value = (() => {
  return 1 + 2 + 3;
})();

Async comptime blocks executed at runtime are not awaited (to avoid forcing async), so they return a Promise, unlike when they're run at compile time. In cases like this, you can provide a fallback for when comptime is disabled:

html := comptime
  fetch 'https://civet.dev' |> await |> .text()
else // fallback for disabled comptime
  '<html></html>'
const html = (() => {
  // fallback for disabled comptime
  return "<html></html>";
})();

Classes

class Animal
  sound = "Woof!"
  bark(): void
    console.log @sound
  wiki()
    fetch 'https://en.wikipedia.org/wiki/Animal'
class Animal {
  sound = "Woof!";
  bark(): void {
    console.log(this.sound);
  }
  wiki() {
    return fetch(
      "https://en.wikipedia.org/wiki/Animal",
    );
  }
}

This

@
id := @id
obj := { @id }
this;
const id = this.id;
const obj = { id: this.id };

Bind

Shorthand for binding methods to their object:

bound := object@.method
bound := object@method
bound := @@method
const bound = object.method.bind(object);
const bound = object.method.bind(object);
const bound = this.method.bind(this);

You can specify arguments to prepend via an immediate function call:

message := console@log "[MSG] "
const message = console.log.bind(
  console,
  "[MSG] ",
);

Private Fields

Private class fields do not need an explicit this. or @ prefix. When accessing a private field of another object, you can rewrite # in place of .#.

class Counter
  #count = 0
  increment(): void
    #count++
  add(other: Counter): void
    #count += other#count if #count in other
  set(#count)
class Counter {
  #count = 0;
  increment(): void {
    this.#count++;
  }
  add(other: Counter): void {
    if (#count in other) {
      this.#count += other.#count;
    }
  }
  set(count) {
    this.#count = count;
  }
}

Static Fields

class A
  @a = 'civet'
class A {
  static a = "civet";
}

Readonly Fields

class B
  b := 'civet'
class B {
  readonly b = "civet";
}

Typed Fields

class C
  size: number | undefined
  @root: Element = document.body
class C {
  size: number | undefined;
  static root: Element = document.body;
}

Getters and Setters

class C
  get x
    @coords?.x ?? 0
  set x(newX)
    @moveTo newX, @coords.y
class C {
  get x() {
    return this.coords?.x ?? 0;
  }
  set x(newX) {
    this.moveTo(newX, this.coords.y);
  }
}

Shorthand for boilerplate getters and setters that delegate (with optional code blocks to run first):

class C
  get #secret
  set #secret
  get @coords.{x,y}
    return 0 unless @coords?
  set @coords.{x,y}
    @coords ?= {}
class C {
  get secret() {
    return this.#secret;
  }
  set secret(value) {
    this.#secret = value;
  }
  get x() {
    if (!(this.coords != null)) {
      return 0;
    }
    return this.coords.x;
  }
  get y() {
    if (!(this.coords != null)) {
      return 0;
    }
    return this.coords.y;
  }
  set x(value1) {
    this.coords ??= {};
    this.coords.x = value1;
  }
  set y(value2) {
    this.coords ??= {};
    this.coords.y = value2;
  }
}
function makeCounter
  count .= 0
  {
    get count
    set count
    increment()
      ++count
  }
function makeCounter() {
  let count = 0;
  return {
    get count() {
      return count;
    },
    set count(value) {
      count = value;
    },
    increment() {
      return ++count;
    },
  };
}

Constructor

class Rectangle
  @(@width: number, @height: number)
class Rectangle {
  constructor(width: number, height: number) {
    this.width = width;
    this.height = height;
  }
}
class Rectangle
  @(public width: number, public height: number)
class Rectangle {
  constructor(
    public width: number,
    public height: number,
  ) {}
}

Static Block

class Civet
  @
    try
      this.colors = getCivetColors()
class Civet {
  static {
    try {
      this.colors = getCivetColors();
    } catch (e) {}
  }
}

Extends

class Civet < Animal
class Civet extends Animal {}

Implements

class Civet < Animal <: Named
class Civet <: Animal, Named
class Civet extends Animal implements Named {}
class Civet implements Animal, Named {}

Decorators

@@Object.seal
class Civet
  @name = "Civet"
@Object.seal
class Civet {
  static name = "Civet";
}

Types

Optional Types

Similar to function parameters and object properties, let declarations and function return values can be declared optional to allow undefined:

let i?: number
i?: number .= undefined
(x?: string)?: string => x
function f(x?: string)?: string
  x
let i: undefined | number;
let i: undefined | number = undefined;
(x?: string): undefined | string => x;
function f(x?: string): undefined | string {
  return x;
}

More generally, T? allows for undefined and T?? additionally allows for null:

let i: number?
let x: string??
let i: number | undefined;
let x: string | undefined | null;

Non-Null Types

T! removes undefined and null from the type:

let x: unknown!
let x: NonNullable<unknown>;

Destructured Typing

Destructured declarations or function arguments can be typed inline using :::

function Component({
  name: [
    first:: string
    last:: string
    ...rest:: string[]
  ]
  counter:: number
  setCounter: sc:: (number) => void
  ...otherProps:: ChildProps
})
function Component({
  name: [first, last, ...rest],
  counter,
  setCounter: sc,
  ...otherProps
}: {
  name: [string, string, ...string[]];
  counter: number;
  setCounter: (number) => void;
} & ChildProps) {}
{
  type:: string
  verb:: "hello" | "goodbye"
} := input
const {
  type,
  verb,
}: {
  type: string;
  verb: "hello" | "goodbye";
} = input;

Unknown

??? is shorthand for the type unknown.

declare function jsonParse(json: string): ???
declare function jsonParse(json: string): unknown;

Signed Number Literals

+1 is invalid in TypeScript but valid in Civet.

declare function sign(n: number): -1 | 0 | +1
declare function sign(n: number): -1 | 0 | 1;

Conditional Types

TypeScript's ternary types can be written using if/unless expressions, with optional else blocks:

let verb:
  if Civet extends Animal
    if Civet extends Cat then "meow"
  else
    string
let breed: unless Civet extends Animal
  then undefined else string
let verb: Civet extends Animal
  ? Civet extends Cat
    ? "meow"
    : never
  : string;
let breed: Civet extends Animal
  ? string
  : undefined;

You can also use < as shorthand for extends, and the negated forms not extends and !<:

let verb: Civet < Cat ? "meow" : string
let breed: Civet !< Animal ? undefined : string
let verb: Civet extends Cat ? "meow" : string;
let breed: Civet extends Animal
  ? string
  : undefined;

You can also use postfix if/unless if the else case is never:

let breed: string if Civet extends Animal
let breed: Civet extends Animal ? string : never;

Import

type { Civet, Cat } from animals
import type { Civet, Cat } from "animals";
{ type Civet, meow } from animals
import { type Civet, meow } from "animals";

CommonJS Import/Export

import fs = require 'fs'
export = fs.readFileSync 'example'
import fs = require("fs");
export = fs.readFileSync("example");

Aliases

::= is shorthand for type aliases:

ID ::= number | string
Point ::= x: number, y: number
type ID = number | string
type Point = x: number, y: number
type ID = number | string;
type Point = { x: number; y: number };
type ID = number | string;
type Point = { x: number; y: number };

Alternatively, you can use type without an =, if the right-hand side is indented (similar to an interface):

type ID
  | number
  | string
type Point
  x: number
  y: number
type ID = number | string;
type Point = {
  x: number;
  y: number;
};

Tuples

Tuple type elements can be named, preventing implicit object literals:

type Point = [number, number]
type Point = [x: number, y: number]
type PointContainer = [(x: number, y: number)]
type Point = [number, number];
type Point = [x: number, y: number];
type PointContainer = [{ x: number; y: number }];

Tuples can also be specified via bullets:

type Point =
  . x: number
  . y: number
type Segment =
  • • x1: number
y1: number
  • • x2: number
y2: number
type Point = [x: number, y: number];
type Segment = [
  [x1: number, y1: number],
  [x2: number, y2: number],
];

Implicit Type Arguments

Like implicit function calls, the angle brackets in type arguments are implicit, and can be replaced by a space or indentation.

let cats: Partial Record CatName, CatInfo
type CatInfo = Partial
  furry: boolean
  fuzzy: boolean?
let cats: Partial<Record<CatName, CatInfo>>;
type CatInfo = Partial<{
  furry: boolean;
  fuzzy: boolean | undefined;
}>;

Interfaces

interface Point
  x: number
  y: number
interface Point {
  x: number;
  y: number;
}
interface Point3D < Point
  z: number
interface Point3D extends Point {
  z: number;
}
interface Signal
  listen(callback: =>): void
interface Signal {
  listen(callback: () => void): void;
}
interface Node<T>
  value: T
  next: Node<T>
interface Node<T> {
  value: T;
  next: Node<T>;
}

Enum

enum Direction
  Up
  Down
  Left = 2 * Down
  Right = 2 * Left
enum Direction {
  Up,
  Down,
  Left = 2 * Down,
  Right = 2 * Left,
}

Indexing Types

Indexed access types can be written with a . when accessing a string, template, or number:

type Age = Person."age"
type First = TupleType.0
type Data = T.`data-${keyof Person}`
type Age = Person["age"];
type First = TupleType[0];
type Data = T[`data-${keyof Person}`];

Note that T.x is reserved for TypeScript namespaces, so you need to add quotes around x for indexed access.

You can also enable CoffeeScript prototype style indexed access:

"civet coffeePrototype"
type Age = Person::age
type Age = Person["age"];

Assertions

data!
data!prop
elt as HTMLInputElement
elt as! HTMLInputElement
data!;
data!.prop;
elt as HTMLInputElement;
elt as unknown as HTMLInputElement;

You can use as tuple to give an array literal a tuple type.

[1, "hello"] as tuple
// type [number, string]
[1, "hello"] as const as tuple
// type [1, "hello"]
[1, "hello"] as const
// type readonly [1, "hello"]
[1, "hello"] satisfies readonly unknown[] | [];
// type [number, string]
[1, "hello"] as const satisfies
  | readonly unknown[]
  | [];
// type [1, "hello"]
[1, "hello"] as const;
// type readonly [1, "hello"]

Modules

from Shorthand

If you have from in your import, you can omit import. You can also omit quotes around most filenames.

fs from fs
{basename, dirname} from path
metadata from ./package.json with type: 'json'
import fs from "fs";
import { basename, dirname } from "path";
import metadata from "./package.json" with { type: "json" };

Import Like Object Destructuring

import {X: LocalX, Y: LocalY} from "./util"
{X: LocalX, Y: LocalY} from "./util"
import { X as LocalX, Y as LocalY } from "./util";
import { X as LocalX, Y as LocalY } from "./util";

Dynamic Import

If it's not the start of a statement, dynamic import does not require parentheses:

{x} = await import url
({ x } = await import(url));

Dynamic Import Declarations

If you're not at the top level, import declarations get transformed into dynamic imports:

function load
  * as utils from ./utils
  { version: nodeVer,
    execPath as nodePath } from process
  fs, {readFile} from fs
  return {utils, nodeVer, nodePath, fs, readFile}
async function load() {
  const utils = await import("./utils");
  const { version: nodeVer, execPath: nodePath } =
    await import("process");
  const ref = await import("fs"),
    fs = ref.default,
    { readFile } = ref;
  return {
    utils,
    nodeVer,
    nodePath,
    fs,
    readFile,
  };
}

INFO

Note that the import gets awaited, so the function becomes asynchronous.

You can also use import declarations as expressions, as a shorthand for awaiting and destructuring a dynamic import:

urlPath := import {
  fileURLToPath, pathToFileURL
} from url
let ref;
const urlPath =
  ((ref = await import("url")),
  {
    fileURLToPath: ref.fileURLToPath,
    pathToFileURL: ref.pathToFileURL,
  });

Export Shorthand

export a, b, c from "./cool.js"
export x = 3
export { a, b, c } from "./cool.js";
export var x = 3;

Export Default Shorthand

Most declarations can also be export default:

export default x := 5
const x = 5;
export default x;

Comments

JavaScript Comments

// one-line comment
/** Block comment
 */
const i /* inline comment */ : number
// one-line comment
/** Block comment
 */
const i /* inline comment */ : number;

Block Comments

###
block comment
/* nested comment */
###
/*
block comment
/* nested comment * /
*/

# Comments

If you do not need private class fields, you can enable # one-line comments (as in many other languages) via a "civet" directive at the beginning of your file:

"civet coffee-comment"
# one-line comment
// one-line comment

JSX

Enhancements, inspired by solid-dsl discussions and jsx spec issues

Indentation

Closing tags are optional if JSX uses indentation.

return
  <>
    <div>
      Hello {name}!
    {svg}
return (
  <>
    <div>Hello {name}!</div>
    {svg}
  </>
);

JSX children do need to be properly indented if they're on separate lines, which prevents pasting improperly indented XHTML/JSX code. (On the other hand, with proper indentation, this feature effectively makes tags like <img> self-closing, bringing JSX closer to HTML than XHTML.) You can turn off this feature using the "civet coffeeJSX" directive.

Implicit Fragments

Adjacent elements/fragments get implicitly combined into one fragment, unless they are items in an array.

return
  <h1>Hello World!
  <div>Body
return (
  <>
    <h1>Hello World!</h1>
    <div>Body</div>
  </>
);
[
  <h1>Hello World!
  <div>Body
]
[<h1>Hello World!</h1>, <div>Body</div>];

XML Comments

<div>
  <!-- Comment -->
  Civet
<div>
  {/* Comment */}
  Civet
</div>;

Attributes

Attribute values do not need braces if they have no whitespace, are indented, or are suitably wrapped (parenthesized expressions, strings and template strings, regular expressions, array literals, braced object literals):

<div
  foo=bar
  count=count()
  sum=x+1
  list=[1, 2, 3]
  string=`hello ${name}`
>
  Civet
<div
  foo={bar}
  count={count()}
  sum={x + 1}
  list={[1, 2, 3]}
  string={`hello ${name}`}
>
  Civet
</div>;
<Show
  when=
    if testing
      timer() > 10
    else
      loaded()
  fallback =
    <img src="loading.gif">
    <div>Loading...</div>
>
<Show
  when={testing ? timer() >