Reference
Civet code on the lefttop, compiled TypeScript output on the rightbottom.
In most cases, the Civet code on the lefttop is optional shorthand. The TypeScript code on the rightbottom (and most TypeScript code) is almost always also valid Civet input.
Variable Declaration
By default, you are responsible for declaring your variables via var
, let
, const
, or their shorthands:
a := 10
b .= 10
c: number | string .= 0
let d: boolean
var v: any
const a = 10;
let b = 10;
let c: number | string = 0;
let d: boolean;
var v: any;
Alternatively, you can use a "civet"
directive at the beginning of your file to specify one of two automatic variable declaration modes:
autoVar
"civet autoVar"
sos = 0
for item of iterable
square = item * item
sos += square
var sos, square;
sos = 0;
for (const item of iterable) {
square = item * item;
sos += square;
}
autoLet
"civet autoLet"
sos = 0
for item of iterable
square = item * item
sos += square
let sos = 0;
for (const item of iterable) {
let square = item * item;
sos += square;
}
Declarations in Conditions and Loops
if match := regex.exec string
console.log match[1], match[2]
let ref;
if ((ref = regex.exec(string))) {
const match = ref;
console.log(match[1], match[2]);
}
if [, dir, base] := /^(.*\/)?([^/]*)$/.exec file
console.log dir, base
let ref;
if (
(ref = /^(.*\/)?([^/]*)$/.exec(file)) &&
Array.isArray(ref) &&
ref.length === 3
) {
const [, dir, base] = ref;
console.log(dir, base);
}
if {x, y} := getLocation()
console.log `At ${x}, ${y}`
else
console.log "Not anywhere"
let ref;
if (
(ref = getLocation()) &&
"x" in ref &&
"y" in ref
) {
const { x, y } = ref;
console.log(`At ${x}, ${y}`);
} else {
console.log("Not anywhere");
}
node .= linkedList.head
while {data, next} := node
console.log data
node = next
let node = linkedList.head;
let ref;
while ((ref = node)) {
const { data, next } = ref;
console.log(data);
node = next;
}
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",
},
};
$:
behaves specially for Svelte compatibility. If you want a key of $
, wrap it in quotes or use explicit braces.
$: document.title = title
"$": "dollar"
{$: "dollar"}
$: document.title = title;
({ $: "dollar" });
({ $: "dollar" });
Braced Literals
With braces, the {x}
shorthand generalizes to any sequence of member accesses and/or calls:
another := {person.name, obj?.c?.x}
computed := {foo(), bar()}
named := {lookup[x+y]}
templated := {`${prefix}${suffix}`: result}
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 templated = {
[`${prefix}${suffix}`]: result,
};
Object Globs
Inspired by brace expansion in shell globs:
point = data{x,y}
point = data.{x,y};
point.{x,y} = data
point3D = { point.{x,y}, z: 0 }
complex := obj.{x:a, b.c()?.y}
merged := data.{...global, ...user};
data.{a, b, ...rest} = result
point = { x: data.x, y: data.y };
point = { x: data.x, y: data.y };
({ x: point.x, y: point.y } = data);
point3D = { x: point.x, y: point.y, z: 0 };
const complex = { x: obj.a, y: obj.b.c()?.y };
const merged = { ...data.global, ...data.user };
({ a: data.a, b: data.b, ...data.rest } = result);
Flagging Shorthand
Inspired by LiveScript:
config := {
+debug
-live
!verbose
}
const config = {
debug: true,
live: false,
verbose: false,
};
Methods and Getters/Setters
Braced objects support methods and getters/setters:
p := {
name: 'Mary'
say(msg)
console.log @name, 'says', msg
setName(@name);
get NAME()
@name.toUpperCase()
}
p.say p.NAME
const p = {
name: "Mary",
say(msg) {
return console.log(this.name, "says", msg);
},
setName(name1) {
this.name = name1;
},
get NAME() {
return this.name.toUpperCase();
},
};
p.say(p.NAME);
TIP
Methods need a body, or they get treated as literal shorthand. To specify a blank body, use ;
or {}
.
Property Access
Many more literals can appear after a .
to access an object property:
json.'long property'
json.`${movie} name`
matrix.0.0
array.-1
json["long property"];
json[`${movie} name`];
matrix[0][0];
array.at(-1);
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`];
Arrays
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 },
];
Rest
Rest properties/parameters/elements are no longer limited to the final position. You may use them in their first or middle positions as well.
[...head, last] = [1, 2, 3, 4, 5]
([...head] = [1, 2, 3, 4, 5]),
([last] = head.splice(-1));
{a, ...rest, b} = {a: 7, b: 8, x: 0, y: 1}
({ a, b, ...rest } = { a: 7, b: 8, x: 0, y: 1 });
function justDoIt(a, ...args, cb) {
cb.apply(a, args)
}
function justDoIt(a, ...args) {
let [cb] = args.splice(-1);
return cb.apply(a, args);
}
You can also omit the name of the rest component:
[first, ..., last] = array
([first, ...ref] = array),
([last] = ref.splice(-1));
Range Literals
[x..y]
includes x
and y
, while [x...y]
includes x
but not y
.
letters := ['a'..'f']
numbers := [1..10]
reversed := [10..1]
indices := [0...array.length]
const letters = ["a", "b", "c", "d", "e", "f"];
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const reversed = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const indices = ((s, e) => {
let step = e > s ? 1 : -1;
return Array.from(
{ length: Math.abs(e - s) },
(_, i) => s + i * step
);
})(0, array.length);
Array/String Slicing
[i..j]
includes i
and j
, while [i...j]
includes i
but not j
. i
and/or j
can be omitted when slicing.
start := numbers[..2]
mid := numbers[3...-2]
end := numbers[-2..]
numbers[1...-1] = []
const start = numbers.slice(0, 1 + 2 || 1 / 0);
const mid = numbers.slice(3, -2);
const end = numbers.slice(-2);
numbers.splice(1, -1 - 1, ...[]);
Strings
Strings can span multiple lines:
console.log "Hello,
world!"
console.log("Hello,\nworld!");
Triple-Quoted Strings
Leading indentation is removed.
console.log '''
<div>
Civet
</div>
'''
console.log(`<div>
Civet
</div>`);
console.log """
<div>
Civet #{version}
</div>
"""
console.log(`<div>
Civet ${version}
</div>`);
console.log ```
<div>
Civet ${version}
</div>
```
console.log(`<div>
Civet ${version}
</div>`);
Regular Expressions
In addition to the usual JavaScript syntax /.../
, you can use ///...///
to write multi-line regular expressions that ignore top-level whitespace and single-line comments:
phoneNumber := ///
^
\+? ( \d [\d-. ]+ )? // country code
( \( [\d-. ]+ \) )? // area code
(?=\d) [\d-. ]+ \d // start and end with digit
$
///
const phoneNumber =
/^\+?(\d[\d-. ]+)?(\([\d-. ]+\))?(?=\d)[\d-. ]+\d$/;
INFO
///
is treated as a comment if it appears at the top of your file, to support TypeScript triple-slash directives. Keep this in mind when trying examples in the Playground.
Operators
All JavaScript/TypeScript Operators
center := min + length / 2
name := user?.name ?? defaultName
typeof x === "string" && x += "!"
result! as string | number
const center = min + length / 2;
const name = user?.name ?? defaultName;
typeof x === "string" && (x += "!");
result! as string | number;
Late Assignment
a + b = c
a + (b = c);
Multi Assignment
(count[key] ?= 0)++
(count[key] ?= 0) += 1
++count *= 2
(count[key] ??= 0), count[key]++;
(count[key] ??= 0), (count[key] += 1);
++count, (count *= 2);
Humanized Operators
a is b
a is not b
a and b
a or b
a not in b
a not instanceof b
a?
a === b;
a !== b;
a && b;
a || b;
!(a in b);
!(a instanceof b)(a != null);
Includes Operator
item is in array
item is not in array
substring is in string
item1 ∈ container ∌ item2 // Unicode
array.includes(item);
!array.includes(item);
string.includes(substring);
container.includes(item1) &&
!container.includes(item2); // Unicode
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?[key]
fun?(arg)
obj?.[key];
fun?.(arg);
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
a instanceof b not instanceof c
x? instanceof Function
a < b && b <= c;
a <= b && b <= c; // Unicode
a == b && b === c && c != d && d !== e;
a === b && b !== c;
a instanceof b && !(b instanceof c);
x != null && x instanceof Function;
instanceof
shorthand
a <? b
a !<? b
a <? b !<? c
a instanceof b;
!(a instanceof b);
a instanceof b && !(b instanceof c);
typeof
shorthand
a <? "string"
a !<? "string"
a instanceof "number"
a not instanceof "number"
typeof a === "string";
typeof a !== "string";
typeof a === "number";
typeof a !== "number";
Logical XOR Operator
a ^^ b
a xor b
a ^^= b
a xor= b
var xor: (a: unknown, b: unknown) => boolean = (
a,
b
) => (a ? !b && a : b);
xor(a, b);
xor(a, b);
a = xor(a, b);
a = xor(a, b);
a !^ b
a xnor b
a !^= b
a xnor= b
var xnor: (a: unknown, b: unknown) => boolean = (
a,
b
) => (a ? b : !b || a);
xnor(a, b);
xnor(a, b);
a = xnor(a, b);
a = xnor(a, b);
Modulo Operator
%
can return negative values, while %%
is always between 0 and the divisor.
let a = -3
let b = 5
let rem = a % b
let mod = a %% b
console.log rem, mod
var modulo: (a: number, b: number) => number = (
a,
b
) => ((a % b) + b) % b;
let a = -3;
let b = 5;
let rem = a % b;
let mod = modulo(a, b);
console.log(rem, mod);
Object.is
The "civet objectIs"
directive changes the behavior of the is
operator to Object.is
, which is a bit better behaved than ===
. The plan is to make this the default behavior, once TypeScript supports type narrowing with Object.is
as well as it does for ===
. (Currently, a is b
will not correctly narrow b
in some edge cases.)
"civet objectIs"
a is b
a is not b
var is: {
<B, A extends B>(a: A, b: B): b is A;
<A, B>(a: A, b: B): a is A & B;
} = Object.is as any;
is(a, b);
!is(a, b);
Pipe Operator
Based on F# pipes and TC39 Proposal: Pipe Operator.
data
|> Object.keys
|> console.log
console.log(Object.keys(data));
Pairs particularly well with single-argument function shorthand:
x.length |> & + 1 |> .toString()
(x.length + 1).toString();
Use await
, yield
, or return
in your pipeline:
fetch url |> await
|> .json() |> await
|> return
return await(await fetch(url)).json();
Pipe assignment:
data |>= .content
data = data.content;
Fat pipes ||>
pass the left-hand value to the next two steps in the pipeline (ignoring the output from the right-hand side):
array
||> .pop()
||> .push 5
||> .sort()
||> .reverse()
array.pop(),
array.push(5),
array.sort(),
array.reverse(),
array;
count |> & + 1
||> console.log
|> & * 2
||> console.log
let ref;
console.log((ref = count + 1)),
console.log((ref = ref * 2)),
ref;
url |> fetch |> await
||> (response) => console.log response.status
|> .json() |> await
||> (json) => console.log "json:", json
|> callback
let ref;
((response) => console.log(response.status))(
(ref = await fetch(url))
),
((json) => console.log("json:", json))(
(ref = await ref.json())
),
callback(ref);
Unicode forms:
data ▷= func1 |▷ func2 ▷ func3
let ref;
(data = func2((ref = func1(data)))), func3(ref);
Await Operators
TC39 proposal: await
operations
await.allSettled promises
await Promise.allSettled(promises);
await.all
for url of urls
fetch url
await Promise.all(
(() => {
const results = [];
for (const url of urls) {
results.push(fetch(url));
}
return results;
})()
);
await.all
for url of urls
async do
fetch url |> await |> .json() |> await
await Promise.all(
(() => {
const results = [];
for (const url of urls) {
results.push(
(async () => {
{
return await (
await fetch(url)
).json();
}
})()
);
}
return results;
})()
);
Custom Infix Operators
You can also define your own infix operators; see Functions as Infix Operators below.
Functions
Function Calls
The parentheses in a function call are usually optional. If present, there should be no space between the function and the open paren.
console.log x, f(x), (f g x), g f x
console.log(x, f(x), f(g(x)), g(f(x)));
Implicit function application also works via indentation, where commas before newlines are optional.
console.log
"Hello"
name
"!"
JSON.stringify
id: getId()
date: new Date
console.log(
"Hello",
name,
"!",
JSON.stringify({
id: getId(),
date: new Date(),
})
);
function
function abort
process.exit 1
function abort() {
return process.exit(1);
}
function circle(degrees: number): {x: number, y: number}
radians := degrees * Math.PI / 180
x: Math.cos radians
y: Math.sin radians
function circle(degrees: number): {
x: number;
y: number;
} {
const radians = (degrees * Math.PI) / 180;
return {
x: Math.cos(radians),
y: Math.sin(radians),
};
}
INFO
Implicit return of the last value in a function can be avoided by specifying a void
return type (or Promise<void>
for async functions), adding a final semicolon or explicit return
, or globally using the directive "civet -implicitReturns"
.
function abort
process.exit 1;
function abort() {
process.exit(1);
}
function abort: void
process.exit 1
function abort(): void {
process.exit(1);
}
function run(command: string): Promise<void>
await exec command
async function run(
command: string
): Promise<void> {
await exec(command);
}
One-Line Functions
function do console.log "Anonymous"
function f do console.log "Named"
function f(x) do console.log x
(function () {
{
return console.log("Anonymous");
}
});
function f() {
{
return console.log("Named");
}
}
function f(x) {
{
return console.log(x);
}
}
Function Overloading
function add(a: string, b: string): string
function add(a: number, b: number): number
a+b
function add(a: string, b: string): string;
function add(a: number, b: number): number {
return a + b;
}
Arrow Functions
INFO
Unlike ECMAScript, zero-argument arrows do not need a ()
prefix, but one-argument arrows do need parentheses around the argument.
abort := => process.exit 1
const abort = () => process.exit(1);
createEffect => console.log data()
greet := (name) => console.log "Hello", name
createEffect(() => console.log(data()));
const greet = (name) =>
console.log("Hello", name);
INFO
=>
makes arrow functions as usual, while ->
makes function
s (which can have this
assigned via .call
).
add := (a: number, b: number) => a + b
const add = (a: number, b: number) => a + b;
add := (a: number, b: number) -> a + b
function add(a: number, b: number) {
return a + b;
}
INFO
Unlike ECMAScript, even multi-line arrow functions implicitly return their last value. See above for how to avoid this behavior.
circle := (degrees: number): {x: number, y: number} =>
radians := degrees * Math.PI / 180
x: Math.cos radians
y: Math.sin radians
const circle = (
degrees: number
): { x: number; y: number } => {
const radians = (degrees * Math.PI) / 180;
return {
x: Math.cos(radians),
y: Math.sin(radians),
};
};
You can also use Unicode arrows:
curryAdd := (a: number) → (b: number) ⇒ a + b
function curryAdd(a: number) {
return (b: number) => a + b;
}
return.value
Instead of specifying a function's return value when it returns, you can prepare it ahead of time using return.value
(or its shorthand, assigning to return
). Using this feature disables implicit return
for that function.
function sum(list: number[])
return .= 0
for item of list
return += item
function sum(list: number[]) {
let ret = 0;
for (const item of list) {
ret += item;
}
return ret;
}
function search<T>(list: T[]): T | undefined
return unless list
for item of list
if match item
return = item
return++ if return.value
list.destroy()
function search<T>(list: T[]): T | undefined {
let ret: T | undefined;
if (!list) {
return ret;
}
for (const item of list) {
if (match(item)) {
ret = item;
}
}
if (ret) {
ret++;
}
list.destroy();
return ret;
}
Single-Argument Function Shorthand
x.map &.name
x.map &.profile?.name[0...3]
x.map &.callback a, b
x.map +&
x.filter (&)
x.map(($) => $.name);
x.map(($1) => $1.profile?.name.slice(0, 3));
x.map(($2) => $2.callback(a, b));
x.map(($3) => +$3);
x.filter(($4) => $4);
INFO
Short function block syntax like 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(($) => $.name);
x.map(($1) => $1?.profile?.name.slice(0, 3));
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
contains(x, y);
!contains(x, y);
You can combine this with a variable declaration:
operator {min, max} := Math
value min ceiling max floor
const { min, max } = Math;
max(min(value, ceiling), floor);
You can also define an operator with a function body:
operator calls<T,R>(t: T, f: (this: T) => R): R
f.call(t)
this calls callback
function calls<T, R>(t: T, f: (this: T) => R): R {
return f.call(t);
}
calls(this, callback);
Operators are just functions in the end, and behave as such when used unambiguously:
operator foo
x foo foo(y, z)
x (foo) y
foo(x, foo(y, z));
x(foo(y));
You can also import
functions from another module as operators (independent of whether they are declared as operators in the other module):
import { operator contains } from 'bar'
x contains y
export operator has(x, y)
y contains x
import { contains } from "bar";
contains(x, y);
export function has(x, y) {
return contains(y, x);
}
Operator Assignment
Even without blessing a function as an operator
, you can use it in an assignment form:
{min, max} := Math
smallest .= Infinity
largest .= -Infinity
for item in items
smallest min= item
largest max= item
const { min, max } = Math;
let smallest = Infinity;
let largest = -Infinity;
for (const item in items) {
smallest = min(smallest, item);
largest = max(largest, item);
}
Conditions
If/Else
if coffee or relaxed
code()
else
sleep()
if (coffee || relaxed) {
code();
} else {
sleep();
}
One-Line If/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'
const name =
power === Infinity
? "Saitama"
: power > 9000
? "Goku"
: void 0;
const caps =
name != null ? name.toUpperCase() : "ANON";
Unless
unless tired
code()
if (!tired) {
code();
}
Postfix If/Unless
civet.speed = 15 if civet.rested
if (civet.rested) {
civet.speed = 15;
}
Switch
switch dir
when '>' then civet.x++
when '<'
civet.x--
civet.x = 0 if civet.x < 0
else civet.waiting += 5
switch (dir) {
case ">": {
civet.x++;
break;
}
case "<": {
civet.x--;
if (civet.x < 0) {
civet.x = 0;
}
break;
}
default: {
civet.waiting += 5;
}
}
With implicit return
:
getX := (civet: Civet, dir: Dir) =>
switch dir
when '>' then civet.x + 3
when '<' then civet.x - 1
when '^' then civet.x + 0.3
const getX = (civet: Civet, dir: Dir) => {
switch (dir) {
case ">": {
return civet.x + 3;
}
case "<": {
return civet.x - 1;
}
case "^": {
return civet.x + 0.3;
}
}
};
Pattern Matching
switch s
""
console.log "nothing"
/\s+/
console.log "whitespace"
"hi"
console.log "greeting"
if (s === "") {
console.log("nothing");
} else if (
typeof s === "string" &&
/\s+/.test(s)
) {
console.log("whitespace");
} else if (s === "hi") {
console.log("greeting");
}
switch a
[]
console.log "empty"
[item]
console.log "one", item
[first, ...middle, last]
console.log "multiple", first, "...", last
else
console.log "not array"
if (Array.isArray(a) && a.length === 0) {
console.log("empty");
} else if (Array.isArray(a) && a.length === 1) {
const [item] = a;
console.log("one", item);
} else if (Array.isArray(a) && a.length >= 2) {
const [first, ...middle] = a,
[last] = middle.splice(-1);
console.log("multiple", first, "...", last);
} else {
console.log("not array");
}
INFO
Array patterns are exact; object patterns allow unmatched properties (similar to TypeScript types).
switch x
{type: "text", content}
console.log `"${content}"`
{type, ...rest}
console.log `unknown type ${type}`
else
console.log 'unknown'
if (
typeof x === "object" &&
x != null &&
"type" in x &&
x.type === "text" &&
"content" in x
) {
const { type, content } = x;
console.log(`"${content}"`);
} else if (
typeof x === "object" &&
x != null &&
"type" in x
) {
const { type, ...rest } = x;
console.log(`unknown type ${type}`);
} else {
console.log("unknown");
}
switch x
[{type: "text", content: /\s+/}, ...rest]
console.log "leading whitespace"
[{type: "text", content}, ...rest]
console.log "leading text:", content
[{type}, ...rest]
console.log "leading type:", type
if (
Array.isArray(x) &&
x.length >= 1 &&
typeof x[0] === "object" &&
x[0] != null &&
"type" in x[0] &&
x[0].type === "text" &&
"content" in x[0] &&
typeof x[0].content === "string" &&
/\s+/.test(x[0].content)
) {
const [{ type, content }, ...rest] = x;
console.log("leading whitespace");
} else if (
Array.isArray(x) &&
x.length >= 1 &&
typeof x[0] === "object" &&
x[0] != null &&
"type" in x[0] &&
x[0].type === "text" &&
"content" in x[0]
) {
const [{ type, content }, ...rest] = x;
console.log("leading text:", content);
} else if (
Array.isArray(x) &&
x.length >= 1 &&
typeof x[0] === "object" &&
x[0] != null &&
"type" in x[0]
) {
const [{ type }, ...rest] = x;
console.log("leading type:", type);
}
INFO
You can also use condition fragments as patterns.
switch x
< 0
console.log "it's negative"
> 0
console.log "it's positive"
is 0
console.log "it's zero"
else
console.log "it's something else"
if (x < 0) {
console.log("it's negative");
} else if (x > 0) {
console.log("it's positive");
} else if (x === 0) {
console.log("it's zero");
} else {
console.log("it's something else");
}
switch x
% 15 is 0
console.log "fizzbuzz"
% 3 is 0
console.log "fizz"
% 5 is 0
console.log "buzz"
else
console.log x
if (x % 15 === 0) {
console.log("fizzbuzz");
} else if (x % 3 === 0) {
console.log("fizz");
} else if (x % 5 === 0) {
console.log("buzz");
} else {
console.log(x);
}
INFO
Aliasing object properties works the same as destructuring.
switch e
{type, key: eventKey}
return [type, eventKey]
if (
typeof e === "object" &&
e != null &&
"type" in e &&
"key" in e
) {
const { type, key: eventKey } = e;
return [type, eventKey];
}
INFO
Patterns can aggregate duplicate bindings.
switch x
[{type}, {type}]
type
if (
Array.isArray(x) &&
x.length === 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;
}
Loops
All JavaScript loops are available, with optional parentheses around the clause.
for let i = 0; i < 100; i++
console.log i
for (let i = 0; i < 100; i++) {
console.log(i);
}
for..of
Looping over an iterator via for..of
defaults to const
:
for item of list
console.log item
for (const item of list) {
console.log(item);
}
for let item of list
item *= item
console.log item
for (let item of list) {
item *= item;
console.log(item);
}
You can also keep track of the current index of the iteration by specifying a comma and a second variable (also defaulting to const
):
for item, index of list
console.log `${index}th item is ${item}`
let i = 0;
for (const item of list) {
const index = i++;
console.log(`${index}th item is ${item}`);
}
for each..of
For Arrays and other objects implementing .length
and [i]
indexing, you can use for each..of
as an optimized form of for..of
(without building an iterator):
for each item of list
console.log item
for (let i = 0, len = list.length; i < len; i++) {
const item = list[i];
console.log(item);
}
for each let item of list
item *= item
console.log item
for (let i = 0, len = list.length; i < len; i++) {
let item = list[i];
item *= item;
console.log(item);
}
for each item, index of list
console.log `${index}th item is ${item}`
for (let i = 0, len = list.length; i < len; i++) {
const index = i;
const item = list[i];
console.log(`${index}th item is ${item}`);
}
for each
loops are similar to Array.prototype.forEach
(hence the name), but are more efficient and allow for e.g. break
and continue
.
for..in
Looping over properties of an object via for..in
defaults to const
:
for key in object
console.log key
for (const key in object) {
console.log(key);
}
for var key in object
console.log key
console.log `Last key is ${key}`
for (var key in object) {
console.log(key);
}
console.log(`Last key is ${key}`);
You can also retrieve the corresponding value by specifying a comma and a second variable (also defaulting to const
):
for key, value in object
console.log `${key} maps to ${value}`
for (const key in object) {
const value = object[key];
console.log(`${key} maps to ${value}`);
}
If your object might have a prototype with enumerable properties, you can skip them with own
:
for own key in object
console.log key
var hasProp: <T>(
object: T,
prop: keyof T
) => boolean = {}.constructor.hasOwn as any;
for (const key in object) {
if (!hasProp(object, key)) continue;
console.log(key);
}
Loop Expressions
If needed, loops automatically assemble an Array of the last value within the body of the loop for each completed iteration.
squares :=
for item of list
item * item
const squares = (() => {
const results = [];
for (const item of list) {
results.push(item * item);
}
return results;
})();
evenSquares :=
for item of list
continue unless item % 2 == 0
item * item
const evenSquares = (() => {
const results = [];
for (const item of list) {
if (!(item % 2 == 0)) {
continue;
}
results.push(item * item);
}
return 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
Because loop expressions wrap in an IIFE, you cannot use return
inside such a loop, nor can you break
or continue
any outer loop.
Loops that use await
automatically get await
ed. If you'd rather obtain the promise for the results so you can await
them yourself, use async for
.
results :=
for url of urls
await fetch url
const results = await(async () => {
const results1 = [];
for (const url of urls) {
results1.push(await fetch(url));
}
return results1;
})();
promise :=
async for url of urls
await fetch url
const promise = (async () => {
const results = [];
for (const url of urls) {
results.push(await fetch(url));
}
return results;
})();
Postfix Loop
console.log item for item of array
for (const item of array) {
console.log(item);
}
Infinite Loop
i .= 0
loop
i++
break if i > 5
let i = 0;
while (true) {
i++;
if (i > 5) {
break;
}
}
Range Loop
for i of [0...array.length]
array[i] = array[i].toString()
for (
let end = array.length, i1 = 0, asc = 0 <= end;
asc ? i1 < end : i1 > end;
asc ? ++i1 : --i1
) {
const i = i1;
array[i] = array[i].toString();
}
for [1..5]
attempt()
for (let i = 1; i <= 5; ++i) {
attempt();
}
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);
Do Blocks
To put multiple lines in a scope and possibly an expression, you can use do
without a while
/until
suffix, similar to TC39 proposal: do
expressions.
x := 5
do
x := 10
console.log x
console.log x
const x = 5;
{
const x = 10;
console.log(x);
}
console.log(x);
x := do
y := f()
y*y
const x = (() => {
{
const y = f();
return y * y;
}
})();
INFO
Because do
expressions wrap in an IIFE, you cannot use return
, break
, or continue
within them.
Async Do Blocks
You can create a promise using await
notation with async do
:
promise := async do
result := await fetch url
await result.json()
const promise = (async () => {
{
const result = await fetch(url);
return await result.json();
}
})();
await Promise.allSettled for url of urls
async do
result := await fetch url
await result.json()
await Promise.allSettled(
(() => {
const results = [];
for (const url of urls) {
results.push(
(async () => {
{
const result = await fetch(url);
return await result.json();
}
})()
);
}
return results;
})()
);
Labels
:outer while (list = next())?
for item of list
if finale item
break outer
continue :outer
outer: while ((list = next()) != null) {
for (const item of list) {
if (finale(item)) {
break outer;
}
}
continue outer;
}
INFO
Labels have the colon on the left to avoid conflict with implicit object literals. The colons are optional in break
and continue
. As a special case, Svelte's $:
can be used with the colon on the right.
$: document.title = title
$: document.title = title;
Classes
class Animal
sound = "Woof!"
bark(): void
console.log @sound
wiki()
fetch 'https://en.wikipedia.org/wiki/Animal'
class Animal {
sound = "Woof!";
bark(): void {
console.log(this.sound);
}
wiki() {
return fetch(
"https://en.wikipedia.org/wiki/Animal"
);
}
}
This
@
id := @id
obj := { @id }
this;
const id = this.id;
const obj = { id: this.id };
Bind
bound := object@.method
bound := object@method
bound := @@method
const bound = object.method.bind(object);
const bound = object.method.bind(object);
const bound = this.method.bind(this);
Private Fields
Private class fields do not need an explicit this.
or @
prefix.
class Counter
#count = 0
increment(): void
#count++
add(other: Counter): void
#count += other.#count if #count in other
set(#count)
class Counter {
#count = 0;
increment(): void {
this.#count++;
}
add(other: Counter): void {
if (#count in other) {
this.#count += other.#count;
}
}
set(count) {
this.#count = count;
}
}
Static Fields
class A
@a = 'civet'
class A {
static a = "civet";
}
Readonly Fields
class B
b := 'civet'
class B {
readonly b = "civet";
}
Typed Fields
class C
size: number | undefined
@root: Element = document.body
class C {
size: number | undefined;
static root: Element = document.body;
}
Getters and Setters
class C
get x
@coords?.x ?? 0
set x(newX)
@moveTo newX, @coords.y
class C {
get x() {
return this.coords?.x ?? 0;
}
set x(newX) {
this.moveTo(newX, this.coords.y);
}
}
Shorthand for boilerplate getters and setters that delegate (with optional code blocks to run first):
class C
get #secret
set #secret
get @coords.{x,y}
return 0 unless @coords?
set @coords.{x,y}
@coords ?= {}
class C {
get secret() {
return this.#secret;
}
set secret(value) {
this.#secret = value;
}
get x() {
if (!(this.coords != null)) {
return 0;
}
return this.coords.x;
}
get y() {
if (!(this.coords != null)) {
return 0;
}
return this.coords.y;
}
set x(value1) {
this.coords ??= {};
this.coords.x = value1;
}
set y(value2) {
this.coords ??= {};
this.coords.y = value2;
}
}
function makeCounter
count .= 0
{
get count
set count
increment()
++count
}
function makeCounter() {
let count = 0;
return {
get count() {
return count;
},
set count(value) {
count = value;
},
increment() {
return ++count;
},
};
}
Constructor
class Rectangle
@(@width: number, @height: number)
class Rectangle {
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
}
class Rectangle
@(public width: number, public height: number)
class Rectangle {
constructor(
public width: number,
public height: number
) {}
}
Static Block
class Civet
@
try
this.colors = getCivetColors()
class Civet {
static {
try {
this.colors = getCivetColors();
} catch (e) {}
}
}
Extends
class Civet < Animal
class Civet extends Animal {}
Implements
class Civet < Animal <: Named
class Civet <: Animal, Named
class Civet extends Animal implements Named {}
class Civet implements Animal, Named {}
Decorators
@@Object.seal
class Civet
@name = "Civet"
@Object.seal
class Civet {
static name = "Civet";
}
Types
Unknown
???
is shorthand for the type unknown
.
declare function jsonParse(json: string): ???
declare function jsonParse(json: string): unknown;
Import
type { Civet, Cat } from animals
import type { Civet, Cat } from "animals";
{ type Civet, meow } from animals
import { type Civet, meow } from "animals";
Aliases
type ID = number | string
type ID = number | string;
type Point = x: number, y: number
type Point = { x: number; y: number };
The =
is optional if the content is indented:
type ID
| number
| string
type Point
x: number
y: number
type ID = number | string;
type Point = {
x: number;
y: number;
};
Interfaces
interface Point
x: number
y: number
interface Point {
x: number;
y: number;
}
interface Point3D < Point
z: number
interface Point3D extends Point {
z: number;
}
interface Signal
listen(callback: =>): void
interface Signal {
listen(callback: () => void): void;
}
interface Node<T>
value: T
next: Node<T>
interface Node<T> {
value: T;
next: Node<T>;
}
Enum
enum Direction
Up
Down
Left = 2 * Down
Right = 2 * Left
enum Direction {
Up,
Down,
Left = 2 * Down,
Right = 2 * Left,
}
Assertions
elt as HTMLInputElement
elt as HTMLInputElement;
Modules
from
Shorthand
If you have from
in your import
, you can omit import
. You can also omit quotes around most filenames.
fs from fs
{basename, dirname} from path
metadata from ./package.json assert type: 'json'
import fs from "fs";
import { basename, dirname } from "path";
import metadata from "./package.json" assert { 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));
Export Shorthand
export a, b, c from "./cool.js"
export x = 3
export { a, b, c } from "./cool.js";
export var x = 3;
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
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(" ")}
/>
</>;
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".
Attributes
Attribute values without whitespace or suitably wrapped (parenthesized expressions, strings and template strings, regular expressions, array literals, braced object literals) do not need braces:
<div
foo=bar
count=count()
sum=x+1
list=[1, 2, 3]
>
Civet
<div
foo={bar}
count={count()}
sum={x + 1}
list={[1, 2, 3]}
>
Civet
</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>
</>;
Comments
<div>
<!-- Comment -->
Civet
<div>
{/* Comment */}
Civet
</div>;
Indentation
Closing tags are optional if JSX uses indentation.
return
<>
<div>
Hello {name}!
{svg}
return (
<>
<div>Hello {name}!</div>
{svg}
</>
);
Implicit Fragments
Adjacent elements/fragments get implicitly combined into one fragment, unless they are items in an array.
return
<h1>Hello World!
<div>Body
return (
<>
<h1>Hello World!</h1>
<div>Body</div>
</>
);
[
<h1>Hello World!
<div>Body
]
[<h1>Hello World!</h1>, <div>Body</div>];
Function Children
<For each=items()>
(item) =>
<li>{item}
<For each={items()}>
{(item) => {
return <li>{item}</li>;
}}
</For>;
SolidJS
link
automatically typed as HTMLAnchorElement
"civet solid"
link := <a href="https://civet.dev/">Civet
import 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">;
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 item
var key, item, index, value;
var hasProp: <T>(
object: T,
prop: keyof T
) => boolean = {}.constructor.hasOwn as any;
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);
}
Double-Quoted Strings
"civet coffeeInterpolation"
console.log "Hello #{name}!"
console.log(`Hello ${name}!`);
CoffeeScript Operators
"civet coffeeEq"
x == y != z
x === y && y !== z;
"civet coffeeIsnt"
x isnt y
x !== y;
"civet coffeeNot"
not (x == y)
not x == y
!(x == y);
!x == y;
"civet coffeeBinaryExistential"
x ? y
x ?? y;
"civet coffeeOf"
item in array
key of object
var indexOf: <T>(
this: T[],
searchElement: T
) => boolean = [].indexOf as any;
indexOf.call(array, item) >= 0;
key in object;
"civet coffeePrototype"
X::
X::a
X.prototype;
X.prototype.a;
CoffeeScript Booleans
"civet coffeeBooleans"
on
off
yes
no
true;
false;
true;
false;
CoffeeScript Comments
If you don't need private class fields, you can enable #
for single-line comments:
"civet coffeeComment"
# one-line comment
// one-line comment
###...###
block comments are always available.