Comparison to JavaScript/TypeScript
Civet aims to be 99% compatible with existing JavaScript/TypeScript code. However, there are a few intentional deviations.
In the examples below, Civet code is on the lefttop, and compiled TypeScript output is on the rightbottom.
Single-Argument Arrow Functions
In Civet, arrow functions with a single argument need to wrap that argument in parentheses; otherwise, it gets treated like an implicit function call.
(x) => x + 1
x => x + 1
(x) => x + 1;
x(() => x + 1);
The advantage of this approach is that zero-argument functions do not need ()
to indicate arguments:
=> console.log("Hello")
createEffect => console.log(signal())
() => console.log("Hello");
createEffect(() => console.log(signal()));
Implicit Return
In Civet, all functions implicitly return the value of their last statement. You can disable this functionality by adding a semicolon at the end of the last statement, declaring a void
return type, or adding an explicit return
.
function more(x) {
x+1
}
function hello() {
console.log("Hello world!")
}
function hello() {
console.log("Hello world!");
}
function hello(): void {
console.log("Hello world!")
}
function hello() {
console.log("Hello world!")
return
}
function more(x) {
return x + 1;
}
function hello() {
return console.log("Hello world!");
}
function hello() {
console.log("Hello world!");
}
function hello(): void {
console.log("Hello world!");
}
function hello() {
console.log("Hello world!");
return;
}
By contrast, in JavaScript, only arrow functions without braces implicitly return a value. This case works in Civet too; just be sure not to have a trailing semicolon which turns off implicit returns. And in Civet you get implicit returns from arrow functions with braces or indented blocks too.
(x) => x + 1
(x) => x + 1;
=> console.log("Hello world!")
=> console.log("Hello world!");
(x) => {
if (x > 5)
"large"
else
"small"
}
(x) => x + 1;
(x) => {
x + 1;
};
() => console.log("Hello world!");
() => {
console.log("Hello world!");
};
(x) => {
if (x > 5) {
return "large";
} else {
return "small";
}
};
If you don't like implicit returns outside of single-line arrow functions, you can turn off this feature entirely via a "civet"
directive at the top of your file:
"civet -implicitReturns"
function hello() {
console.log("Hello world!")
}
=>
console.log("Hello world!")
(x) => x + 1
function hello() {
console.log("Hello world!");
}
() => {
console.log("Hello world!");
};
(x) => x + 1;
Operator Spacing
Because Civet allows for implicit function calls without parentheses, symbol operators (+
, -
, etc.) need to be spaced consistently:
- Unary operators (
+
,-
,~
,!
) cannot have spaces after them. - Binary operators (
+
,-
,*
,**
,/
,%
,%%
,==
,===
,<
,>
,<=
,>=
,<<
,>>
,>>>
,&
,&&
,|
,||
,??
) should either have spaces on both sides, or no space on either side. (Currently we also allow space after but not before the operator.) - Comma operator is forbidden in some contexts, e.g., array/object indexing. If you run into this limitation, wrap in parentheses.
- Ternary operator
x ?y :z
needs space before both?
and:
(in particular to distinguish from unary postfixx?
). - Type parameters and arguments
<T>
cannot have spaces before them. - Regular expression literals cannot start with a space. (Use
\
or[ ]
.)
x+y // addition
x + y // addition
x +y // unary
//invalid: + y
x + y; // addition
x + y; // addition
x(+y); // unary
//invalid: + y
x/y/z // division
x / y / z // division
x /y/ z // regular expression
//invalid: x /y
x / y / z; // division
x / y / z; // division
x(/y/(z)); // regular expression
//invalid: x /y
x<y>z // comparison
x < y > z // comparison
// JSX:
x <y> z
//invalid: x <y
x < y && y > z; // comparison
x < y && y > z; // comparison
// JSX:
x(<y> z</y>);
//invalid: x <y
Comments
Civet supports ///...///
regular expression blocks. Thus, you should avoid comments that start with a triple slash — unless they are at the start of the file, as in TypeScript triple-slash directives.
/// A comment because at the top of file
if s
/// not a comment
///.exec s
/// A comment because at the top of file
if (s) {
/notacomment/.exec(s);
}
Indentation
Because Civet allows for indented blocks as shorthand for braced blocks, it generally requires you to respect your own indentation.
(x) =>
console.log("Hello")
x+1
(x) => {
console.log("Hello");
return x + 1;
};
if (condition)
console.log("condition holds")
console.log("condition still holds")
if (condition) {
console.log("condition holds");
console.log("condition still holds");
}
By contrast, in JavaScript, the last indented line would have been treated as if it were at the top level.
Braced Blocks
Civet allows you to wrap your blocks in braces (or not), but to avoid conflicts with braced object literals, there are some restrictions. In particular, a one-line braced block must be on the same line as the if
etc. it's part of:
// block
if (x) { y }
// not a block
if (x)
{ y }
// block
if (x) {
y;
}
// not a block
if (x) {
}
({ y });
Automatic Semicolon Insertion
JavaScript has complicated rules for automatic semicolon insertion. Civet has a different set of rules for what separates different statements, which are hopefully closer to your intuition. Binary operators and member access can continue on the next line, but only when spaced in the natural way (e.g. space after the binary operator).
x +
y
- z
-negative
[array]
.length
{name: value}
x + y - z;
-negative;
[array].length;
({ name: value });
By contrast, JavaScript would treat the code on the lefttop as three binary operators with an index access (and a member access), followed by a code block with a label.
Keywords
Civet has some additional keywords beyond JavaScript's and TypeScript's, preventing them from being variable names. These include and
, or
, is
, not
, unless
, and until
. If you use these variables in your code, please rename them, e.g., by appending an underscore.
Labels
In Civet, labels are written :label
instead of label:
.
:label while (true) {
break label
}
label: while (true) {
break label;
}
(This is to enable most object literals not needing braces.)
Decorators
Civet uses @
as shorthand for this
, static
, and constructor
. Decorators need to be written with @@
:
@@Object.seal
class Civet
@name = "Civet"
@Object.seal
class Civet {
static name = "Civet";
}
Civet also does not support decorators on the same line as methods. This lets you use implicit function call syntax:
class Civet
@@description translate "Caffeine time!"
drink()
@fetch @coffeeCup
class Civet {
@description(translate("Caffeine time!"))
drink() {
return this.fetch(this.coffeeCup);
}
}
JSX
To allow for automatic closing of JSX tags, Civet requires JSX children to be properly indented.
<div>
<h1>Hello</h1>
<p>Text</p>
"not inside div anymore"
<div>
<h1>Hello</h1>
<p>Text</p>
</div>;
("not inside div anymore");
If you're OK with explicitly closing all tags, you can turn off the indentation requirement via a "civet"
directive:
"civet coffeeJSX"
<div>
<h1>Hello</h1>
<p>Text</p>
still inside div
</div>
<div>
<h1>Hello</h1>
<p>Text</p>
still inside div
</div>;
Another discrepancy is that Civet automatically combines consecutive JSX tags at the same indentation level into a JSX fragment. (Otherwise, only the last tag would serve a purpose.)
<h1>Hello</h1>
<p>Text</p>
<>
<h1>Hello</h1>
<p>Text</p>
</>;
Sloppy Mode Features
Civet generally assumes (but does not enforce) that you are following JavaScript's strict mode, so it does not support:
Anything Else
If some existing JS/TS code does not parse in Civet and it does not fall into one of the categories above, it is likely a bug; please report it.
Comparison to CoffeeScript
If you are coming to Civet from a CoffeeScript background, check out this comparison.