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: anyconst 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 += squarevar 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 += squarelet 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 += squarelet 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 = 1024var 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, basefunction 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 = nextlet 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 += numberlet 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 datalet ref;
while (
!(
(ref = attempt()) &&
typeof ref === "object" &&
"status" in ref &&
ref.status === "OK" &&
"data" in ref
)
);
const { 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 };
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"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"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} = resultpoint = { 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.NAMEconst 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.-1json["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?firstjson?.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) =>
((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"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 },
];
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
arg2const 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, cconst rotate = [c, -s, s, c];
func.apply @,
• arg1
• arg2func.apply(this, [arg1, arg2]);
people :=
. name: "Alice"
id: 7
. name: "Bob"
id: 9const 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] = arraylet 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]var range: (
start: number,
end: number,
) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
const letters = ["a", "b", "c", "d", "e", "f"];
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const indices = range(0, array.length);
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]var range: (
start: number,
end: number,
) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
const indices = range(0, array.length);
const strict = range(a + 1, b);
// [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]var revRange: (
start: number,
end: number,
) => number[] = (start, end) => {
const length = start - end;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = start - i;
}
return arr;
};
const reversed = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const reversedIndices = revRange(
array.length - 1,
0 - 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] = []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]const strict = numbers.slice(first + 1, last);
Slices are increasing by default, but you can reverse them with > or >=:
reversed := x[..>=]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]][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%]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!"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
</div>
"""console.log(`<div>
Civet
</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, and interpolates ${expression} like in template literals:
phoneNumber := ///
^
\+? ( \d [\d-. ]+ )? // country code
( \( [\d-. ]+ \) )? // area code
(?=\d) [\d-. ]+ \d // start and end with digit
$
///const phoneNumber =
/^\+?(\d[\d-. ]+)?(\([\d-. ]+\))?(?=\d)[\d-. ]+\d$/;
r := /// ${prefix} \s+ ${suffix} ///const r = RegExp(`${prefix}\\s+${suffix}`);
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()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 := :iteratorconst magicSymbol = Symbol.magic;
const iteratorSymbol = Symbol.for("iterator");
Symbol names that aren't valid identifiers can be wrapped in quotes:
magicSymbol := :"magic-symbol"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 | numberconst 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 = ca + (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);
Multi Destructuring
Use name^pattern to assign name while also destructuring into pattern:
[first^{x, y}, ...rest] = points([first, ...rest] = points), ({ x, y } = first);
Shorthand for destructuring an object property and its contents:
{name^: {first, last}} = person({ name } = person), ({ first, last } = name);
INFO
Multi destructuring also works in declarations, function parameters, for loops, and pattern matching.
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 // Unicodearray.includes(item);
!array.includes(item);
string.includes(substring);
container.includes(item1) &&
!container.includes(item2); // Unicode
Concat Operator
a ++ b ++ c
[1,2,3] ⧺ resta.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 ++= bvar 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] = valueobj != 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 Functiona < 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.groupfunction 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 !<? ca 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= btype 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= btype 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, divvar 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, modvar 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 %% bvar 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 bvar 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.logconsole.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 |> addRecordaddRecord(JSON.parse(data) as MyRecord);
Use await, throw, yield, yield*, or return in your pipeline:
fetch url |> await
|> .json() |> await
|> returnreturn await(await fetch(url)).json();
Pipe assignment:
data |>= .contentdata = 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.loglet 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
|> callbacklet 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 ▷ func3let ref;
data = func2((ref = func1(data)));
func3(ref);
Await Operators
TC39 proposal: await operations
await.allSettled promisesawait Promise.allSettled(promises);
await.all
for url of urls
fetch urlawait 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() |> awaitawait 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()]);
Alternatively, you can use array literals:
// Sequential
await [ first(), second() ]
await
. first()
. second()
// Parallel
await.all [ first(), second() ]
await.all
. first()
. second()// 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:
| Unicode | ASCII | Unicode | ASCII | Unicode | ASCII | Unicode | ASCII |
|---|---|---|---|---|---|---|---|
≤ | <= | ≥ | >= | ≠ | != | ≡ | == |
≣ | === | ≢ | !== | ≔ | := | ⁇ | ?? |
‖ | || | ≪ | << | ≫ | >> | ⋙ | >>> |
… | ... | ‥ | .. | ∈ | is in | ∉ | is not in |
▷ | |> | → | -> | ⇒ | => | ’s | 's |
⧺ | ++ | — | -- | ÷ | %/ | • | . bullet |
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 xconsole.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 Dateconsole.log(
"Hello",
name,
"!",
JSON.stringify({
id: getId(),
date: new Date(),
}),
);
function
function abort
process.exit 1function 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 radiansfunction 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 1function abort(): void {
process.exit(1);
}
function run(command: string): Promise<void>
await exec commandasync function run(
command: string,
): Promise<void> {
await exec(command);
}
function count()
yield 1
yield 2
yield 3function* 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+bfunction 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 1const abort = () => process.exit(1);
createEffect => console.log data()
greet := (name) => console.log "Hello", namecreateEffect(() => 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 + bconst add = (a: number, b: number) => a + b;
add := (a: number, b: number) -> a + bconst 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 radiansconst 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 + bconst curryAdd = function (a: number) {
return (b: number) => a + b;
};
Pin Parameters and This Parameters
You can directly assign an argument to an outer variable foo by writing ^foo (see pins from pattern matching):
let resolve, reject
promise := new Promise (^resolve, ^reject) =>let resolve, reject;
const promise = new Promise(
(resolve1, reject1) => {
resolve = resolve1;
reject = reject1;
},
);
Similarly, you can directly assign an argument to this.foo by writing @foo (see @ shorthand for this). This is particularly useful within methods.
@promise := new Promise (@resolve, @reject) =>const promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
this.promise = promise;
Parameter Multi Destructuring
Multi destructuring applies to function parameters:
function Component(props^{
name^: {first, last},
counter
})function Component(props) {
const { name, counter } = props,
{ first, last } = name;
}
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 += itemfunction 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 (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)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:", .($) => 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()($) => 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 (&), 0xfffnumbers.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 ycontains(x, y);
!contains(x, y);
!contains(x, y);
You can combine this with a variable declaration:
operator {min, max} := Math
value min ceiling max floorconst { 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 callbackfunction 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) yfoo(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 ximport { 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 cimport { 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 fa < 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 + jfunction 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 dimport { 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);
You can specify both precedence and associativity together in either order:
operator mult arguments tighter (+)
a mult b mult c + d mult e mult fmult(a, b, c) + mult(d, e, f);
Instead of declaring operators in multiple files, you can declare global operators (with custom precedence and associativity) in a project configuration file. Within a Civet source file, you can redeclare with operator to override the precedence and/or associativity (but the default behavior is that specified in the configuration file).
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= itemconst { 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.restedif (civet.rested) {
civet.speed = 15;
}
For complex conditions that involve indented function application, the condition can be indented:
sleep() unless
(or)
coffee
relaxedif (!(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.3const 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 { 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:", typeif (
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 [{}, ...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 [{ 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);
}
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");
}
You can add a binding before a condition fragment:
switch f()
x % 15 is 0
console.log "fizzbuzz", x
x % 3 is 0
console.log "fizz", x
x % 5 is 0
console.log "buzz", xlet m;
if (((m = f()), m % 15 === 0)) {
const x = m;
console.log("fizzbuzz", x);
} else if (m % 3 === 0) {
const x = m;
console.log("fizz", x);
} else if (m % 5 === 0) {
const x = m;
console.log("buzz", x);
}
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];
}
Patterns can aggregate duplicate bindings:
switch x
[{type}, {type}]
typefunction 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;
}
Object properties with value matchers (other than a renaming identifier) are not bound by default (similar to object destructuring assignment). Add a trailing ^ to bind them:
switch x
{type^: /list/, content^: [first, ...]}
console.log type, content, firstif (
typeof x === "object" &&
x != null &&
"type" in x &&
typeof x.type === "string" &&
/list/.test(x.type) &&
"content" in x &&
Array.isArray(x.content) &&
x.content.length >= 1
) {
const { type, content } = x,
[first, ...ref] = content;
console.log(type, content, first);
}
More generally, use name^pattern or name^ pattern (multi destructuring) to bind name while also matching pattern:
switch x
[space^ /^\s*$/, number^ /^\d+$/, ...]
console.log space, numberif (
Array.isArray(x) &&
x.length >= 2 &&
typeof x[0] === "string" &&
/^\s*$/.test(x[0]) &&
typeof x[1] === "string" &&
/^\d+$/.test(x[1])
) {
const [space, number, ...ref] = x;
console.log(space, number);
}
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", contentif (
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 ifor (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 itemfor (const item of list) {
console.log(item);
}
for let item of list
item *= item
console.log itemfor (let item of list) {
item *= item;
console.log(item);
}
for var item of list
console.log item
console.log "Last item:", itemfor (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}`);
}
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"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 itemfor (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 itemfor (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 keyfor (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 keyvar 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, valuefor (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 * itemconst results = [];
for (const item of list) {
results.push(item * item);
}
const squares = results;
evenSquares :=
for item of list
continue unless item % 2 == 0
item * itemconst 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.
You can also accumulate multiple items and/or spreads:
function flatJoin<T>(list: T[][], sep: T): T[]
for sublist, i of list
if i
sep, ...sublist
else
...sublistfunction flatJoin<T>(list: T[][], sep: T): T[] {
let i1 = 0;
const results = [];
for (const sublist of list) {
const i = i1++;
if (i) {
results.push(sep, ...sublist);
} else {
results.push(...sublist);
}
}
return results;
}
flatImage :=
for x of [0...nx]
...for y of [0...ny]
image.get x, yconst results = [];
for (let i = 0; i < nx; ++i) {
const x = i;
for (let i1 = 0; i1 < ny; ++i1) {
const y = i1;
results.push(image.get(x, y));
}
}
const flatImage = results;
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 objectconst 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 urlconst results1 = [];
for (const url of urls) {
results1.push(await fetch(url));
}
const results = results1;
promise :=
async for url of urls
await fetch urlconst 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 * nconst 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 itemfunction* mapConcatIter(f, a, b) {
for (const item of a) {
yield f(item);
}
for (const item of b) {
yield f(item);
}
}
Use ...x to yield all items from another iterable x via yield*:
function concatIter(iters)
for* iter of iters
...iterfunction* concatIter(iters) {
for (const iter of iters) {
yield* iter;
}
}
Postfix Loop
console.log item for item of arrayfor (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 * vconst 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 === 0const 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 objectlet 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 === 0let results = true;
for (const item of array) {
if (!(item % 2 === 0)) {
results = false;
break;
}
}
const allEven = results;
emptyOwn := for every own key in objectvar 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 objectlet 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 first returns the first body value. If there is no body, it uses the item being looped over. Combined with a when condition, this can act like Array.prototype.find.
firstEven :=
for first item of array when item % 2 === 0
firstEvenSquare :=
for first item of array when item % 2 === 0
item * itemlet results = undefined;
for (const item of array) {
if (!(item % 2 === 0)) continue;
results = item;
break;
}
const firstEven = results;
let results1 = undefined;
for (const item of array) {
if (!(item % 2 === 0)) continue;
results1 = item * item;
break;
}
const firstEvenSquare = 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 arraylet 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 itemlet 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 arraylet 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}\n`let results = "";
for (const item of array) {
results += `[${item.type}] ${item.title}\n`;
}
const all = results;
for concat concatenates the body values as arrays, using the concat operator ++. If there is no body, it uses the item being looped over.
function flat1<T>(arrays: T[][]): T[]
for concat array of arraysvar 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
);
function flat1<T>(arrays: T[][]): T[] {
let results = [];
for (const array of arrays) {
concatAssign(results, array);
}
return results;
}
Implicit bodies in for sum/product/min/max/join/concat reductions can use a single destructuring:
xMin := for min {x} of pointslet 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 pointslet 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]
}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
}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)
}const rateLimits = {
admin: Infinity,
...(() => {
const results = {};
for (const user of users) {
Object.assign(results, {
[user.name]: getRemainingLimit(user),
});
}
return results;
})(),
};
For Loop Multi Destructuring
Multi destructuring applies to for..of/in loops:
for item^[key, value] of map
if value and key.startsWith "a"
process itemfor (const item of map) {
const [key, value] = item;
if (value && key.startsWith("a")) {
process(item);
}
}
for person^{name^: {first, last}, age} of people
console.log first, last, age, personfor (const person of people) {
const { name, age } = person,
{ first, last } = name;
console.log(first, last, age, person);
}
for key, value^{x, y} in items
if x > y
process key, valuefor (const key in items) {
const value = items[key],
{ x, y } = value;
if (x > y) {
process(key, value);
}
}
Infinite Loop
i .= 0
loop
i++
break if i > 5let 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;
i1 < end;
++i1
) {
const i = i1;
array[i] = array[i].toString();
}
for i of [0...n] by 2
console.log i, "is even"for (let i1 = 0; i1 < n; i1 += 2) {
const i = i1;
console.log(i, "is even");
}
for [1..5]
attempt()for (let i = 1; i <= 5; ++i) {
attempt();
}
for i of [1..]
attempt ifor (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]for (let i1 = first; i1 <= last; ++i1) {
const i = i1;
console.log(array[i]);
}
for i of [left<..<right]
console.log array[i]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++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 :outerouter: 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, except for Civet reserved words (e.g. and) where a colon is required. JavaScript reserved words are invalid as labels.
Iterations also get implicit labels if you refer to them by type, via break/continue for/while/until/loop/do:
loop
while list = next()
for item of list
if item is 'skip'
continue for
else if item is 'next'
continue while
else if item is 'done'
break loop_loop: while (true) {
_while: while ((list = next())) {
_for: for (const item of list) {
if (item === "skip") {
continue _for;
} else if (item === "next") {
continue _while;
} else if (item === "done") {
break _loop;
}
}
}
}
Controlling Loop Value
function varVector(items, mean)
for item of items
continue with 0 unless item?
item -= mean
item * itemfunction 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 itemlet 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.lengthfunction 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 resultlet 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 e <? MyError
console.log "MyError", e.data
catch <? RangeError, <? ReferenceError
console.log "R...Error"
catch e^{message^: /bad/}
console.log "bad", message
throw e
catch e
console.log "other", etry {
foo();
} catch (e1) {
if (e1 instanceof MyError) {
const e = e1;
console.log("MyError", e.data);
} else 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 e = e1,
{ message } = e;
console.log("bad", message);
throw e;
} 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:/}try {
foo();
} catch (e) {
if (
typeof e === "object" &&
e != null &&
"message" in e &&
typeof e.message === "string" &&
/^EPIPE:/.test(e.message)
) {
const {} = 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 nodelet 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 xconst x = 5;
{
const x = 10;
console.log(x);
}
console.log(x);
x := do
y := f()
y*ylet 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()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+3const value = 6;
console.log "3rd triangular number is", comptime
function triangle(n) do n and n + triangle n-1
triangle 3console.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 };
class Person
getName()
@name
setName(@name)class Person {
getName() {
return this.name;
}
setName(name) {
this.name = name;
}
}
Bind
Shorthand for binding methods to their object:
bound := object@.method
bound := object@method
bound := @@methodconst 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.bodyclass 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.yclass 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
@ is also shorthand for constructor. @arg arguments create typed fields if the fields are not already declared.
class Rectangle
@(@width: number, @height: number)class Rectangle {
width: number;
height: number;
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 < Animalclass Civet extends Animal {}
Implements
class Civet < Animal <: Named
class Civet <: Animal, Namedclass Civet extends Animal implements Named {}
class Civet implements Animal, Named {}
Long implements lists can be indented:
class Civet
extends Animal
implements
Named
Record string, numberclass Civet
extends Animal
implements Named, Record<string, number> {}
Mixins
Civet experimentally defines a "mixin" to be a function mapping a class to a class (e.g., returning a subclass with added functionality), and provides with syntax for applying one or more mixins to the base class you're extending:
function Movable(C)
class extends C
move(@x, @y)
class Civet extends Animal with Movable
@()
@move 0, 0function Movable(C) {
return class extends C {
move(x, y) {
this.x = x;
this.y = y;
}
};
}
class Civet extends Movable(Animal) {
constructor() {
this.move(0, 0);
}
}
Mixins get applied left to right. The extended class defaults to Object.
class Civet with Mixin1, Mixin2class Civet extends Mixin2(Mixin1(Object)) {}
Long mixin lists can be indented:
class Civet
extends Animal
with
Mixin1 arg
Mixin2class Civet extends Mixin2(Mixin1(arg)(Animal)) {}
Decorators
Civet uses @ for this, so decorators need to use @@:
@@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
xlet i: undefined | number;
let i: undefined | number = undefined;
(x?: string): undefined | string => x;
function f(x?: string): undefined | string {
return x;
}
More generally, type 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;
To allow for type inference and the initial undefined value:
let x?let x = undefined;
To allow for later assignment of undefined while specifying an initial value (currently limited to a literal or member expression):
let x? = 5
let y? = xlet x: undefined | number = 5;
let y: undefined | typeof x = x;
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"
} := inputconst {
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 | +1declare function sign(n: number): -1 | 0 | 1;
Function Types
Like arrow functions, arrow types can use => or -> (with equivalent meanings) and can omit parameters and/or return type:
function f(callback: ->)
callback()function f(callback: () => void) {
return callback();
}
Arrow types can use an async prefix as shorthand for Promise return types:
function f(callback: async =>)
callback()type AutoPromise<T> = Promise<Awaited<T>>;
function f(callback: () => AutoPromise<void>) {
return callback();
}
Similarly, async functions (except generators) get their return type automatically wrapped in Promise:
function f(): number
await fetch 'https://civet.dev'
.statustype AutoPromise<T> = Promise<Awaited<T>>;
async function f(): AutoPromise<number> {
return await fetch("https://civet.dev").status;
}
INFO
You can still include explicit Promise wrappers in your return types. The AutoPromise helper avoids adding an extra Promise wrapper.
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 stringlet 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 : stringlet 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 Animallet breed: Civet extends Animal ? string : never;
Import
type { Civet, Cat } from animalsimport type { Civet, Cat } from "animals";
{ type Civet, meow } from animalsimport { 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: numbertype 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: numbertype 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: numbertype 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: numberinterface Point {
x: number;
y: number;
}
interface Point3D < Point
z: numberinterface Point3D extends Point {
z: number;
}
interface Signal
listen(callback: =>): voidinterface 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 * Leftenum 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::agetype Age = Person["age"];
Assertions
data!
data!prop
elt as HTMLInputElement
elt as! HTMLInputElementdata!;
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"]
Unlike TypeScript, assertions can start later lines:
"Hello, world!"
.split /\s+/
as [string, string]"Hello, world!".split(/\s+/) as [string, string];
for value of [1, 2, 3]
value ** 2
as [number, number, number](() => {
const results = [];
for (const value of [1, 2, 3]) {
results.push(value ** 2);
}
return results;
})() as [number, number, number];
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/promises
{basename, dirname} from path
metadata from ./package.json with type: 'json'import fs from "fs/promises";
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
url := import * from url
Foo := import default from Foolet ref;
const urlPath = {
fileURLToPath: (ref = await import("url"))
.fileURLToPath,
pathToFileURL: ref.pathToFileURL,
};
const url = await import("url");
const Foo = (await import("Foo")).default;
Export Shorthand
export a, b, c from "./cool.js"
export x = 3export { a, b, c } from "./cool.js";
export var x = 3;
Export Default Shorthand
Most declarations can also be export default:
export default x := 5const x = 5;
export default x;
Backward Import/Export
Similar to Python, you can put from before import/export. This can improve autocompletion behavior.
from fs/promises import { readFile, writeFile }
from ./util import * as util
from ./package.json with {type: 'json'} export { version }import { readFile, writeFile } from "fs/promises";
import * as util from "./util";
export { version } from "./package.json" with { type: "json" };
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>Bodyreturn (
<>
<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() > 10 : loaded()}
fallback={
<>
<img src="loading.gif" />
<div>Loading...</div>
</>
}
/>;
Arbitrary braced literals convert to equivalent JSX:
<div {foo}>Civet
<div {props.name}>Civet
<div {data()}>Civet<>
<div foo={foo}>Civet</div>
<div name={props.name}>Civet</div>
<div data={data()}>Civet</div>
</>;
Call/member/glob/spread expressions without unwrapped whitespace do not need braces (but note that simple identifiers remain empty attributes):
<div foo>Civet
<div data()>Civet
<div @name>Civet
<div @@onClick>Civet
<div modal@onClick>Civet
<div props{name, value}>Civet
<div ...foo>Civet<>
<div foo>Civet</div>
<div data={data()}>Civet</div>
<div name={this.name}>Civet</div>
<div onClick={this.onClick.bind(this)}>
Civet
</div>
<div onClick={modal.onClick.bind(modal)}>
Civet
</div>
<div name={props.name} value={props.value}>
Civet
</div>
<div {...foo}>Civet</div>
</>;
Computed property names:
<div [expr]={value}>Civet
<div `data-${key}`={value}>Civet<>
<div {...{ [expr]: value }}>Civet</div>
<div {...{ [`data-${key}`]: value }}>Civet</div>
</>;
Element id
<div #foo>Civet
<div #{expression}>Civet<>
<div id="foo">Civet</div>
<div id={expression}>Civet</div>
</>;
Class
<div .foo>Civet
<div .foo.bar>Civet
<div .{expression}>Civet
<div .button.{size()}><>
<div class="foo">Civet</div>
<div class="foo bar">Civet</div>
<div class={expression || ""}>Civet</div>
<div
class={["button", size()]
.filter(Boolean)
.join(" ")}
/>
</>;
Specify the "civet react" directive to use the className attribute instead:
"civet react"
<div .foo>Civet<div className="foo">Civet</div>;
Implicit Element
<.foo>Civet<div class="foo">Civet</div>;
"civet defaultElement=span"
<.foo>Civet<span class="foo">Civet</span>;
INFO
Implicit elements must start with id or class shorthand (# or .).
Boolean Toggles
<Component +draggable -disabled !hidden><Component
draggable={true}
disabled={false}
hidden={false}
/>;
TIP
! is synonymous with - and both say "set the attribute value to false".
Declaration Children
Lexical declarations can appear in the middle of JSX, allowing you to effectively define variables in the middle of a long JSX block.
<div>
{ {first, last} := getName() }
Welcome, {first} {last}!let first, last;
<div>
{(({ first, last } = getName()), void 0)}
Welcome, {first} {last}!
</div>;
Function Children
Arrow functions are automatically wrapped in braces:
<For each=items()>
(item) =>
<li>{item}<For each={items()}>
{(item) => {
return <li>{item}</li>;
}}
</For>;
Code Children
Civet expressions can be prefixed with > instead of wrapping in braces.
<main>
>for item of items
<li>{item}<main>
{(() => {
const results = [];
for (const item of items) {
results.push(<li>{item}</li>);
}
return results;
})()}
</main>;
<.greeting>
>if user := getUser()
<span>
Welcome back,
>' '
> user |> .name |> formatName
<LogoutButton>
else
<LoginButton><div class="greeting">
{(() => {
let ref;
if ((ref = getUser())) {
const user = ref;
return (
<>
<span>
Welcome back, {formatName(user.name)}
</span>
<LogoutButton />
</>
);
} else {
return <LoginButton />;
}
})()}
</div>;
To write an entire block of code, indent it inside > (or >do — a do block is implicit).
<.user>
>
user := getUser()
`Logged in as ${user.first} ${user.last}`<div class="user">
{(() => {
{
const user = getUser();
return `Logged in as ${user.first} ${user.last}`;
}
})()}
</div>;
Automatic Code Children
If code is more common than text in your JSX, consider using the `"civet jsxCode" directive which treats JSX children as Civet expressions.
"civet jsxCode"
function List({items})
<ul>
for item of items
<li>
if item.image
<img src=item.image>
if item.type is "link"
<a href=item.url>
'Link: '
item.title
else
<p> item.contentfunction List({ items }) {
return (
<ul>
{(() => {
const results = [];
for (const item of items) {
results.push(
<li>
{item.image ? (
<img src={item.image} />
) : (
void 0
)}
{item.type === "link" ? (
<a href={item.url}>
{"Link: "}
{item.title}
</a>
) : (
<p>{item.content}</p>
)}
</li>,
);
}
return results;
})()}
</ul>
);
}
Note that each "line" is treated as its own child expression, which makes it easy to concatenate multiple expressions. If you need a multi-line block, use do:
"civet jsxCode"
<div>
"Welcome, "
do
{ first, last } := getName()
`${first} ${last.toUpperCase()}`<div>
{"Welcome, "}
{(() => {
{
const { first, last } = getName();
return `${first} ${last.toUpperCase()}`;
}
})()}
</div>;
Declaration children work too, but note that they are exposed to the outer block:
"civet jsxCode"
<div>
"Welcome, "
{ first, last } := getName()
`${first} ${last.toUpperCase()}`let first, last;
<div>
{"Welcome, "}
{(({ first, last } = getName()), void 0)}
{`${first} ${last.toUpperCase()}`}
</div>;
You can also individually control whether children get treated as code when on the same line as the tag ("civet jsxCodeSameLine") or when indented ("civet jsxCodeNested"). (This distinction is only meaningful when "civet coffeeJSX" is disabled.)
"civet jsxCodeNested"
<form>
<h2>Enter your name
for part of ["first", "last"]
<label>
part.toUpperCase() + ": "
<input id=part>
<button>Submit<form>
<h2>Enter your name</h2>
{(() => {
const results = [];
for (const part of ["first", "last"]) {
results.push(
<label>
{part.toUpperCase() + ": "}
<input id={part} />
</label>,
);
}
return results;
})()}
<button>Submit</button>
</form>;
SolidJS
link automatically typed as HTMLAnchorElement
"civet solid"
link := <a href="https://civet.dev/">Civetimport type { JSX as JSX } from "solid-js";
type IntrinsicElements<
K extends keyof JSX.IntrinsicElements,
> =
JSX.IntrinsicElements[K] extends JSX.DOMAttributes<
infer T
>
? T
: unknown;
const link = (
<a href="https://civet.dev/">Civet</a>
) as any as IntrinsicElements<"a">;
You can specify whether the code will run on the client (default) and/or the server:
"civet solid client server"
link := <a href="https://civet.dev/">Civetimport type { JSX as JSX } from "solid-js";
type IntrinsicElements<
K extends keyof JSX.IntrinsicElements,
> =
JSX.IntrinsicElements[K] extends JSX.DOMAttributes<
infer T
>
? T
: unknown;
const link = (
<a href="https://civet.dev/">Civet</a>
) as any as string | IntrinsicElements<"a">;
CoffeeScript Compatibility
Turn on full CoffeeScript compatibility mode with a "civet coffeeCompat" directive at the top of your file, or use more specific directive(s) as listed below. You can also selectively remove features, such as "civet coffeeCompat -coffeeForLoops -autoVar".
CoffeeScript For Loops
"civet coffeeForLoops autoVar"
for item, index in array
console.log item, index
for key, value of object
console.log key, value
for own key, value of object
console.log key, value
for item from iterable
console.log itemvar key, item, index, value;
var hasProp: <T>(
object: T,
prop: PropertyKey,
) => boolean = ({}.constructor as any).hasOwn;
for (
let i = 0, len = array.length;
i < len;
i++
) {
item = array[(index = i)];
console.log(item, index);
}
for (key in object) {
value = object[key];
console.log(key, value);
}
for (key in object) {
if (!hasProp(object, key)) continue;
value = object[key];
console.log(key, value);
}
for (item of iterable) {
console.log(item);
}
CoffeeScript Do Blocks
This option disables Civet do blocks and do...while loops.
"civet coffeeDo"
do foo
do (url) ->
await fetch urlfoo()(async function (url) {
return await fetch(url);
})(url);
CoffeeScript Interpolation
"civet coffeeInterpolation"
console.log "Hello #{name}!"
console.log """
Goodbye #{name}!
"""console.log(`Hello ${name}!`);
console.log(`Goodbye ${name}!`);
"civet coffeeInterpolation"
r = /// #{prefix} \s+ #{suffix} ///r = RegExp(`${prefix}\\s+${suffix}`);
CoffeeScript Operators
"civet coffeeEq"
x == y != zx === y && y !== z;
"civet coffeeIsnt"
x isnt yx !== y;
"civet coffeeNot"
not (x == y)
not x == y!(x == y);
!x == y;
"civet coffeeDiv"
x // yvar div: (a: number, b: number) => number = (
a,
b,
) => Math.floor(a / b);
div(x, y);
"civet coffeeBinaryExistential"
x ? yx ?? y;
"civet coffeeOf"
item in array
key of objectvar indexOf: <T>(
this: T[],
searchElement: T,
) => number = [].indexOf as any;
indexOf.call(array, item) >= 0;
key in object;
"civet coffeePrototype"
X::
X::aX.prototype;
X.prototype.a;
CoffeeScript Booleans
"civet coffeeBooleans"
on
off
yes
notrue;
false;
true;
false;
CoffeeScript Ranges
"civet coffeeRange"
[10..1]
[a..b]
[a...b]var revRange: (
start: number,
end: number,
) => number[] = (start, end) => {
const length = start - end;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = start - i;
}
return arr;
};
var range: (
start: number,
end: number,
) => number[] = (start, end) => {
const length = end - start;
if (length <= 0) return [];
const arr = Array(length);
for (let i = 0; i < length; ++i) {
arr[i] = i + start;
}
return arr;
};
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
((s, e) =>
s > e ? revRange(s, e - 1) : range(s, e + 1))(
a,
b,
);
((s, e) =>
s > e ? revRange(s, e) : range(s, e))(a, b);
CoffeeScript Comments
If you don't need private class fields or length shorthand, you can enable # for single-line comments:
"civet coffeeComment"
# one-line comment// one-line comment
"civet coffeeComment"
r = ///
\s+ # whitespace
///r = /\s+/;
###...### block comments are always available.
CoffeeScript Line Continuations
"civet coffeeLineContinuation"
loop
x += \
'hello'while (true) {
x += "hello";
}
CoffeeScript Classes
"civet coffeeClasses autoVar"
class X
privateStaticVar = 5
@publicStaticVar = 6
constructor: (@x) ->
get: -> @x
bound: => @x
@staticFunc: -> @publicStaticVarvar X;
X = (() => {
var privateStaticVar;
privateStaticVar = 5;
return class X {
static publicStaticVar = 6;
constructor(x) {
this.bound = this.bound.bind(this);
this.x = x;
}
get() {
return this.x;
}
bound() {
return this.x;
}
static staticFunc() {
return this.publicStaticVar;
}
};
})();
IIFE Wrapper
In some CommonJS contexts (e.g., browser scripts or concatenating files together), it is helpful to wrap the entire program in an IIFE so that var declarations do not become globals. (CoffeeScript does so by default unless you request bare mode or use ESM features.) In Civet, you can request such a wrapper via the "civet iife" directive:
"civet iife"
var x = 5 // not global thanks to wrapper
x += x * x(() => {
var x = 5; // not global thanks to wrapper
return (x += x * x);
})();
In this mode, you cannot use export, but you can use import thanks to dynamic import declarations. Top-level await turns into a CommonJS-compatible async function call:
"civet iife"
fetch 'https://civet.dev'
|> await
|> .status
|> console.log(async () => {
return console.log(
(await fetch("https://civet.dev")).status,
);
})();
One context where IIFE wrapping is helpful is when building Civet REPLs, such as Civet's CLI or Playground, where we want to display the last computed value; the IIFE wrapper returns this value automatically (wrapped in a Promise in the case of top-level await). In the REPL context, we want to expose (not hide) top-level declarations to the global scope for future REPL inputs. Civet offers a special directive for this behavior:
"civet repl"
x .= 5 // outer block => exposed
if x++
y .= x * x // inner block => not exposed
x += yvar x;
(() => {
x = 5; // outer block => exposed
if (x++) {
let y = x * x; // inner block => not exposed
return (x += y);
}
return;
})();
INFO
Exposed declarations become var regardless of their original declaration type (var, const, let). In particular, const semantics is not preserved. This behavior is usually helpful in a REPL context: it lets you attempt to correct a previous declaration. It also matches Chrome's console behavior (but not NodeJS's CLI behavior).
JavaScript Compatibility
Civet aims to be mostly compatible with JavaScript and TypeScript, so that most JavaScript/TypeScript code is valid Civet code. See comparison for the few exceptions. You can increase JavaScript compatibility with the following options:
No Implicit Returns
To disable implicit returns from functions, use the directive "civet -implicitReturns":
"civet -implicitReturns"
function processAll(array)
for item of array
process itemfunction processAll(array) {
for (const item of array) {
process(item);
}
}
Strict Mode
Output from Civet runs by default in JavaScript's sloppy mode, unless it is loaded as an ECMAScript module. To turn on JavaScript's strict mode, add the directive "use strict" as usual, or the Civet directive "civet strict". The latter is useful because it can be specified in Civet configuration files.
"civet strict"
x = 5 // invalid"use strict";
x = 5; // invalid
Civet