Skip to content
This repository

Assignment

nondeclaring assignment :=

Assigns to an existing variable without declaring a new one.

$ coco -bce 'a = 1; new -> a := 2; b = 3'
var a;
a = 1;
new function(){
  var b;
  a = 2;
  b = 3;
};

$ coco -ce 'x := 0'
assignment to undeclared variable "x" on line 1

This is essential because (unlike CoffeeScript) our = always declares the variable in current scope.

Normal compound assignments can be desugared using this operator: a += 1 <=> a := a + 1

ref. https://github.com/jashkenas/coffee-script/issues/712

conditional assignments && +=

Any assignment operator can be conditional when prefixed with &&, ||, ? or !?.

$ coco -bce 'a.b |||= c.d ?= e'
var ref$;
a.b || (a.b |= (ref$ = c.d) != null ? ref$ : c.d = e);

$ coco -bce '[x, y] &&= z'
var x, y;
x && (x = z[0]), y && (y = z[1]);

unary assignments ! = x

Compound assignments for unary operators.

$ coco -bcs
  # syntax: UnaryOp+ AssignOp LHSExpression
  +    = x  # numberize
  !!   = x  # enbool
  -~-~ = x  # intcasting bicrement

var x;
x = +x;
x = !!x;
x = -~-~x;

assignment defaults (o || {}) -> [a ? b] = c

You can specify a default value to each function parameter or destructuring target, using one of logical operators (|| && ? !?).

$ coco -bce '(o || {}) -> [@a ? b] = c'
(function(o){
  var ref$;
  o || (o = {});
  return this.a = (ref$ = c[0]) != null ? ref$ : b, c;
});
  • For parameters, = can be used in place of ?.

subdestructuring x[a, b] = y x{a, b} = y

Performs destructuring into an object's properties.

$ coco -bcs
  x[a, b] = y
  x{a, b} = y

x[a] = y[0], x[b] = y[1];
x.a = y.a, x.b = y.b;

named destructuring ({p, q}:o) ->

You can provide a name to a destructuring pattern, which will be used for the intermediate variable instead of a temporary one.

$ coco -bcs
  ([p, q]:a) ->
  {{x}:b, [y]:c} = o  # short for {b: {x}:b, c: [y]:c}

var b, x, c, y;
(function(a){
  var p, q;
  p = a[0], q = a[1];
});
b = o.b, x = b.x, c = o.c, y = c[0];

soak assign a? = b

Performs assignment only when the right-hand-side value exists.

$ coco -bcs
  [, left, rite]? = /(\w+)\s*=\s*(\w+)/exec input
  ({when, where}?) -> go where, when

var left, rite, ref$;
if ((ref$ = /(\w+)\s*=\s*(\w+)/.exec(input)) != null) {
  left = ref$[1], rite = ref$[2];
}

lef <>

References the left-hand-side of nearest assignment, letting you build your own compound assignments.

$ coco -bcs
  document.querySelector('#wrapme').textContent = "(#<>)"

var ref$;
(ref$ = document.querySelector('#wrapme')).textContent = "(" + ref$.textContent + ")";
  • Does not cross-scope. E.g. f = -> <> is invalid.
  • Stands for left reference.

explicit variable declarations

constant const x = 0

Performs assignments and flags the declared variables as read-only. Redeclaration/reassignment on them will be a compile error.

$ coco -s
  const a = 0
  a++

SyntaxError: increment of constant "a" on line 2

uninitialized var x

Plain ol' var without initialization and redeclaration.

$ coco -ps
  var a, b
  a is b is undefined

true

$ coco -e '(x) -> var x'
SyntaxError: redeclaration of "x" on line 1

exporting export x, y

Creates the same name property of target expression(s) on exports or top-level this.

$ coco -bcs
  export a, b.c
  export
    d: e
    function f then g
  export const h = 1

var h, out$ = typeof exports != 'undefined' && exports || this;
out$.a = a;
out$.c = b.c;
out$.d = e;
out$.f = f;
function f(){
  return g;
}
out$.h = h = 1;
  • Named expressions (variable, dot-identifier access, function declaration, class) are exported with those names.
  • All other expressions get all own properties exported.

Property Access

dot X

In addition to identifiers, our dot operators accept many more things as their right operand. Numbers, strings, parentheses, brackets and braces.

$ coco -bce 'x.0."1".(2).[3]'
x[0]["1"][2][3];

Therefore we don't technically have the bracket notation for property access in {Java,Coffee}Script sense. o[x] is just short for o.[x].

accessignment .= .+=

Compound assignments for ..

$ coco -bce 'location.href.=replace /^http:/, \https:'
location.href = location.href.replace(/^http:/, 'https:');

$ coco -bce 'a.+=0'
a += a[0];

$ coco -bce '[b, c].=reverse()'
var b, c, ref$;
ref$ = [b, c].reverse(), b = ref$[0], c = ref$[1];
  • Is at same precedence as ..
  • Consumes all dot/call chains to the right.

binding access .~

Retrieves an object's method as bound to the object.

$ coco -bce o.p.~m
bind$(o.p, 'm');
function bind$(obj, key){
  return function(){ return obj[key].apply(obj, arguments) };
}

$ coco -ps
  [2 5 9]map 42~toString
  # desugars to: [2, 5, 9].map -> 42.toString.apply 42, arguments

[ '101010', '132', '46' ]

In other words, o.~m sort of emulates o.m.bind(o) in ES5.

array slice x[a, b]

Postfix brackets can take multiple arguments, in which case they collect retrieved values into an array.

$ coco -bce 'x[a, b]'
[x[a], x[b]];

object slice x{a, b: c} _

Postfix curly braces extract the selected properties into a new object.

$ coco -bce 'x{a: b, c}'
({
  a: x.b,
  c: x.c
});

length star [*]

An asterisk within an indexer represents the length of the indexee.

$ coco -bce 'a[*] = b'
a[a.length] = b;

$ coco -bce 'arr()[* - 1]'
var ref$;
(ref$ = arr())[ref$.length - 1];

$ coco -bce '{(* >> 1): mid} = a'
var mid;
mid = a[a.length >> 1];

semiautovivification .@ .@@

Ensures that the property exists as an object or array. x.@y desugars to (x.y ||= {}).

$ coco -bcs
  root.@obj.@@arr.push it
  A@D@@I

var ref$;
((ref$ = root.obj || (root.obj = {})).arr || (ref$.arr = [])).push(it);
(ref$ = A.D || (A.D = {})).I || (ref$.I = []);

Function

extended parameters

$ coco -se
  setXYZ = (
    point  # target object to set the coordinates
    point.x, point.y, point.z
  ) -> point

  setXYZ {} 2 3 5

{ x: 2, y: 3, z: 5 }

Function parameters can be:

  • written multi-line, using the same syntax as array items.
  • any assignable expressions.

function declaration function f(a, b) then

Declares or creates a named function, without touching the nasty JScript bug.

$ coco -bcs
  # syntax: "function" Name? ParameterList? Block
  function equal a, b
    a is b
  add = function (x, y)
    x + y
  ~function bound then this

var add, this$ = this;
function equal(a, b){
  return a === b;
}
add = (function(){
  function add(x, y){
    return x + y;
  }
  return add;
}());
function bound(){
  return this$;
}

$ coco -s
  function f
    f = ->

SyntaxError: redeclaration of function "f" on line 2
  • Parentheses around parameter list can be omitted in the same way that f(a) can be f a.
  • Prefix ~ to bind parent this.
  • The declared variable is read-only.

implicit argument it

Represents the first argument of the current function, like in Groovy's closure. Available only when no parameters are declared.

$ coco -bce 'I = -> it'
var I;
I = function(it){
  return it;
};

do-call do f

A unary operator that calls a function with no arguments, helping you write less parentheses.

$ coco -bce 'do f; do ->'
f();
(function(){})();

bang-call f!

Sugar for ().

$ coco -bce 'f! g?!'
f()(typeof g == 'function' ? g() : void 8);

IIFE sugars

with arguments let this = a, b = c then ...

Short for the JS idiom (function(c){...}.call(a, b)).

$ coco -bcs
  # syntax: "let" ThisAssignment? Assignments* Block
  let (this = a, b = c)
    'use strict'
    assert @ is a
    assert b is c

  r = let      # short for `let ()`
    let c, @d  # short for `let (c = c, d = @d)`
      c + d

var r;
(function(a){
  assert(a === b);
}.call(this, b));
r = (function(){
  return (function(c, d){
    return c + d;
  }.call(this, c, this.d));
}.call(this));

with new context new then ...

Short for new ->, saving precious two key strokes.

$ coco -bcs
  point = new
    @x = 9
    @y = 6

var point;
point = new function(){
  this.x = 9;
  this.y = 6;
};

$ coco -se
  # pseudo object comprehension
  flip = (obj) -> new
    @[v] = k for own k, v in obj

  flip \flop

{ f: '0', l: '1', o: '2', p: '3' }    

As in class constructor, no implicit return is generated.

thisplat f(...) _

Shorthand to delegate the current context to a function.

$ coco -bce 'f ...'
f.apply(this, arguments);

backcall <- f _

Sugar for flattening nested callbacks.

$ coco -bcs
  <~ f
  e <- g this
  return if e
  ( x
    y
  ) <- h ..., z

var this$ = this;
f(function(){
  return g(this$, function(e){
    if (e) {
      return;
    }
    return h(function(x, y){}, z);
  });
});
  • Can only appear on the top level of a block.
  • Consumes all consecutive lines of that block as the function body.
  • <- for plain, <~ for bound.
  • ... can be used to specify the callback position when there are other arguments after it.

hushing auto-return !(a) -> a <-! f

Negating a function literal or the callee of a backcall suppresses the implicit return.

$ coco -bcs
  setTimeout !->
    !function say
      console.log it
    <-! setTimeout
    say \yes

setTimeout(function(){
  function say(it){
    console.log(it);
  }
  setTimeout(function(){
    say('yes');
  });
});

paren-free method chains o.f x, y .g z

Implicit calls don't cross spaced dots, allowing you to chain methods without parentheses.

$ coco -bcs
  obj.foo x, y .bar z
     .baz!

obj.foo(x, y).bar(z).baz();

don't make functions within a loop

Function literals within loops are defined outside.

$ coco -bcs
  sums = for a of as
    a.reduce (x, y) -> x + y

var a, sums, res$, i$, ref$, len$;
res$ = [];
for (i$ = 0, len$ = (ref$ = as).length; i$ < len$; ++i$) {
  a = ref$[i$];
  res$.push(a.reduce(fn$));
}
sums = res$;
function fn$(x, y){
  return x + y;
}

Construction

do-block do then

do with an indented block represents a pair of parenthesis (unless followed by while/until), either as grouping or calling.

$ coco -bcs
  r = do
    f do
      a
      b
    g()

var r;
r = (f(a, b), g());

simple class

Our class defines the constructor as a bare function and properties (prototype members) as bare objects, both on top level under the class block.

$ coco -bcs
  class exports.C extends P
    /* constructor */
    -> super ...

    /* properties */
    member: 'U'
    method: -> super @member, ..static()

    /* any code */
    $private = 42
    @static = -> $private

var C;
exports.C = C = (function(superclass){
  /* constructor */
  C.displayName = 'C';
  var $private, prototype = extend$(C, superclass).prototype, constructor = C;
  function C(){
    superclass.apply(this, arguments);
  }
  /* properties */
  prototype.member = 'U';
  prototype.method = function(){
    return superclass.prototype.method.call(this, this.member, constructor['static']());
  };
  /* any code */;
  $private = 42;
  C['static'] = function(){
    return $private;
  };
  return C;
}(P));
function extend$(sub, sup){
  function fun(){} fun.prototype = (sub.superclass = sup).prototype;
  (sub.prototype = new fun).constructor = sub;
  if (typeof sup.extended == 'function') sup.extended(sub);
  return sub;
}

In short,

class C extends P
  -> /* constructor */
  {'properties'}
  ...

desugars roughly to

C = let superclass = P
  function C
    /* constructor */
  C.displayName = 'C'
  C extends P
  {prototype} = constructor = C
  let this = C
    prototype <<< {'properties'}
    ...
  C

The constructor:

  • receives displayName property, useful for debugging, reflection etc.
  • is accessible as constructor (or ..).

bound constructor

Using the wavy arrow ~> for constructor makes the class to be new-free, like native Array which works with or without new.

$ coco -bce 'Bound = class then (@name) ~>'
var Bound;
Bound = (function(){
  Bound.displayName = 'Bound';
  var prototype = Bound.prototype, constructor = Bound;
  function Bound(name){
    var this$ = this instanceof ctor$ ? this : new ctor$;
    this$.name = name;
    return this$;
  } function ctor$(){} ctor$.prototype = prototype;
  return Bound;
}());

This form allows the constructor to return non-object values. Make sure to return this when you're returning the instance early.

simple super

super is an alias to superclass or the nearest method (a function defined via top-level object under class). When a super starts an access/call chain, the next call auto-passes current this.

$ coco -bce 'super.classMethod!'
superclass.classMethod.call(this);

$ coco -bce 'super::anotherMethod!'
superclass.prototype.anotherMethod.call(this);

mixins implements M, N

You can specify mixin objects to be merged into prototype at the tail of class line.

$ coco -bce 'class implements M, N then O'
(function(){
  var prototype = constructor.prototype;
  importAll$(prototype, arguments[0]);
  importAll$(prototype, arguments[1]);
  O;
  function constructor(){}
  return constructor;
}(M, N));
function importAll$(obj, src){
  for (var key in src) obj[key] = src[key];
  return obj;
}

loop

in range for from to til by

A generic way to loop within certain numeric ranges.

$ coco -bce 'for i from x to y then'
var i, to$;
for (i = x, to$ = y; i <= to$; ++i) {}

$ coco -bce 'for i from x til y then'
var i, to$;
for (i = x, to$ = y; i < to$; ++i) {}

$ coco -bce 'for i from x to y by -1 then'
var i, to$;
for (i = x, to$ = y; i >= to$; --i) {}

$ coco -bce 'for i from x to y by z then'
var i, step$, to$;
for (i = x, to$ = y, step$ = z; step$ < 0 ? i >= to$ : i <= to$; i += step$) {}

$ coco -bce 'for i til x then'
var i, to$;
for (i = 0, to$ = x; i < to$; ++i) {}
  • to for inclusive, til for exclusive.
  • by optionally specifies the step value.
  • from clause is optional, implying from 0.

scoped

IIFE infusion

IIFE sugars (let/with/new) within for body automatically capture the loop variables.

$ coco -s
  fs = for i til 3
    let then -> console.log i
  fs.forEach -> do it

0
1
2

$ coco -bce 'for k in o then new -> say k'
var k;
for (k in o) {
  new fn$(k);
}
function fn$(k){
  say(k);
}
for-let

A tighter version of for-then-let, keeping the loop variables local to the inner scope.

$ coco -s
  n = 42
  for let n to 2
    process.on \exit -> console.log n
  console.log n

42
0
1
2

postconditional _

The traditional do-while (or until) loop.

$ coco -bcs
  # syntax: "do" Block "while"|"until" Condition
  do
    do then
    while x
  until y

do {
  do {} while (x);
} while (!y);

with update clause while i < l, ++i

while/until accepts an update clause after test clause, enabling full emulation of JS for wrt continue.

$ coco -bcs
  until ok(), step()
    continue if hop()
    jump()

for (; !ok(); step()) {
  if (hop()) {
    continue;
  }
  jump();
}

with else clause while f() then g() else h()

Preconditional loops can have an else clause, which runs when the loop body didn't run.

$ coco -bce 'while f() then g() else h()'
var yet$;
for (yet$ = true; f();) {
  yet$ = false;
  g();
} if (yet$) {
  h();
}

forever for ever

Sugar for while true.

$ coco -bce 'continue for ever'
for (;;) {}

switching switch case default

switch (as in JS) with multiple conditions and auto break insertions.

$ coco -bcs
  switch n
  case 1     then 2
  case 3 4   then 5
  case [6 7] then 8; fallthrough
  default 9

  switch
  case a
    break
  case c[d, e]
    f

switch (n) {
case 1:
  2;
  break;
case 3:
case 4:
  5;
  break;
case 6:
case 7:
  8;
  // fallthrough
default:
  9;
}
switch (false) {
case !a:
  break;
case !(c[d] || c[e]):
  f;
}

Basically the same as CoffeeScript's switch-when-else, except ours:

  • requires less indentation.
    • case/default are placed at the same level as switch.
  • supports falling through.
    • If the last expression of case block is fallthrough, it becomes a comment instead of break.
  • expands arrays in case conditions.

Incidentally this is pretty similar to Go's switch.

case can be omitted. This form is handy for refactoring nested ifs, e.g.:

unless a()
  b()
  unless c()
    d()

switch
  break if a()
  b()
  break if c()
  d()

auto-catch

$ coco -bce 'try x catch throw e'

var e;
try {
  x;
} catch (e$) {
  e = e$;
  throw e;
}

$ coco -pe 'try throw 0 catch'
0

The catchee:

  • can be omitted and defaults to e.
  • is function-scoped. See coffee#2422.
  • is returned when the body is empty.

anaphoric if/while/switch-case that

Each that in those blocks implicitly refers to the condition value.

$ coco -bcs
  that if a
  that while b?
  switch c case d then that
  switch case e then that

var that;
if (that = a) {
  that;
}
while ((that = b) != null) {
  that;
}
switch (that = c) {
case d:
  that;
}
switch (false) {
case !(that = e):
  that;
}

cond if => a then b else c

An alternative syntax for chained if-elses, reminiscent of Arc's if, Clojure's cond etc.

$ coco -bcs
  # syntax: IF INDENT (Condition Block)+ (ELSE Block)? DEDENT
  r = if
    a
      b
    c then d
    else
      e
var r;
r = a
  ? b
  : c ? d : e;

labels :label

$ coco -bce ':label block or expression'
label: {
  block || expression;
}

$ coco -se
:pythagoras for a from 1 to 9
  for b from a to 99
    h = Math.sqrt a*a + b*b
    if h % 1 == 0
      console.log a, b, h
      continue pythagoras
:plain
   \end
   break plain
   \unreachable

3 4 5
5 12 13
6 8 10
7 24 25
8 15 17
9 12 15
end

The syntax is backward from JS to disambiguate from the implicit object literal.

Labeling a function (literal, IIFE sugar, or backcall) gives it a name instead of creating a useless block.

$ coco -bcs
  :f ->
    :g let
       <-:h f

(function f(){
  return (function g(){
    return f(function h(){});
  }.call(this));
});

Operator

property copy <<< <<<<

Infix operators that copy enumerable properties from the right operand to left, and return the left value.

$ coco -bce 'x <<< y <<<< z'
import$(importAll$(x, y), z);
function importAll$(obj, src){
  for (var key in src) obj[key] = src[key];
  return obj;
}
function import$(obj, src){
  var own = {}.hasOwnProperty;
  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
  return obj;
}

$ coco -bce 'import {y, (z)}'
this.y = y;
this[z] = z;
  • <<< is for own properties. It gets optimized to a series of assignments if the right operand is an object literal.
  • import and import all are available as aliases.

importing import a, b

Copies properties from target object(s) into current this.

$ coco -bcs
  import a, b
  import all c
  import
    f: g
    h: i

import$(this, a);
import$(this, b);
importAll$(this, c);
this.f = g;
this.h = i;
function import$(obj, src){
  var own = {}.hasOwnProperty;
  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
  return obj;
}
function importAll$(obj, src){
  for (var key in src) obj[key] = src[key];
  return obj;
}

clone ^o

Unary ^ creates a prototypal child of the operand.

$ coco -pe '[c = ^(p = {0}); c.0; c.hasOwnProperty 0; p.isPrototypeOf c]'
[ {}, 0, false, true ]

$ coco -bce 'child = ^parent'
var child;
child = clone$(parent);
function clone$(it){
  function fun(){} fun.prototype = it;
  return new fun;
}

any-instanceof o instanceof [C, K]

A bare array to the right of instanceof is expanded into or chains.

$ coco -bce 'o instanceof [C, K]'
o instanceof C || o instanceof K;

pluck v = delete o.p

If used in an expression, delete returns the original value as opposed to the useless JS behavior.

$ coco -bce 'f delete @x; delete @y'
var ref$;
f((ref$ = this.x, delete this.x, ref$));
delete this.y;

inexistence !?

The counterpart of ?.

$ coco -bce '@a !?= b !? c.d!?'
this.a != null && (this.a = typeof b == 'undefined' || b === null
  ? b
  : c.d == null);

pow **

$ coco -bce '@x **= y ** z'
this.x = Math.pow(this.x, Math.pow(y, z));

The precedence and associativity are set to the same as *//, not to complicate the hierarchy further.

min/max <? >?

Binary operators that return the lesser/greater value of the two.

$ coco -bce 'r = a <? b; @c >?= d'
var r;
r = a < b ? a : b;
this.c >= d || (this.c = d);

$ coco -pe '4 >? 3 + 2'
5
  • Precedence is same as <<.

unary spread +[a, b]

Unary operators affect each item when applied to an array literal.

$ coco -bcs
  @inverts = ~[a, b]
  ++player<[str def]>

this.inverts = [~a, ~b];
++player['str'], ++player['def'];

block arrow =>

Introduces a block, setting the indentation level to the start of the next token.

$ coco -bcs
  if a => b
          c

if (a) {
  b;
  c;
}

Overloaded

string/array repetition 'x' * y

When the left operand of * is a (syntactically obvious) string/array, it is repeated [right operand] times.

$ coco -bcs
  [1] * 2
  'x' * 3
  'z' * y

[1, 1];
'xxx';
repeatString$('z', y);
function repeatString$(str, n){
  for (var r = ''; n > 0; (n >>= 1) && (str += str)) if (n & 1) r += str;
  return r;
}

The helper function (generated when the right operand is large or non-constant) uses O(logN) algorithm, making "#x" * y more efficient than the idiom Array(-~y).join('x').

array joining a * '-'

When the right operand of * is a string literal, [].join is called on the left.

$ coco -bce 'a * "-"'
var join$ = [].join;
join$.call(a, "-");

string subtraction/division x - /r/ y / 's'

When the right operand of -// is a string/regex literal, ''.replace/''.split is called on the left.

$ coco -bce 'x - /r/; y / "s"'
var replace$ = ''.replace, split$ = ''.split;
replace$.call(x, /r/, '');
split$.call(y, "s");

$ coco -pe '({} - /\w/g) / " "'
[ '[', ']' ]

Literal

hyphened identifiers is-array

Hyphened alphabets are allowed within an identifier in place of their upper case representations.

$ coco -bce 'is-array'
isArray;

$ coco -pe 'Array.is-array []'
true

undefined/no-op literal void

A literal that compiles to an undefined value.

$ coco -bcs
  void
  (void, a) -> void
  [,,]

(function(arg$, a){});
[void 8, void 8];
  • Simply ignored when on top-level or returned.
  • Works as a placeholder within function parameters or destructuring assignment.
  • Implied when , follows (, [, or another ,.

word literal \plus \+

Simple strings without whitespace can be written by prefixing a backslash rather than enclosing with single quotes.

$ coco -bce '\\ + \" + \word + \"'
'\\' + '"' + 'word' + '"';

Note that closing symbols () ] } , ;) are disallowed after the first character, so that you can naturally write:

prompt(\?, \!)
  • Syntax borrowed from Clojure's character literal.

quoted words <[ q w ]>

Sugar for a bracketted list of strings, either as an array or indexer.

$ coco -bce 'f <[ array of strings ]>'
f(["array", "of", "strings"]);

$ coco -bce 'o<[ property names ]>'
[o['property'], o['names']];

dynakey {(a+b): c} {"#key": val}

Property names within object literals can be dynamically set using parentheses or interpolated strings.

$ coco -bce 'o = {(paren): 1, "str#ing": 2, (shorthand = 3)}'
var shorthand, o, ref$, ref2$;
o = (ref$ = {}, ref$[paren] = 1, ref$["str" + ing] = 2, ref$[ref2$ = shorthand = 3] = ref2$, ref$);

object splat {...o}

Mixes an object's own properties into the created object.

$ coco -bcs
  poq = {p, ...o, q}
  abc =
    a: true
    ...: b
    ...: c

var poq, abc, ref$;
poq = (ref$ = {
  p: p
}, import$(ref$, o), ref$.q = q, ref$);
abc = (ref$ = {
  a: true
}, import$(ref$, b), import$(ref$, c));
function import$(obj, src){
  var own = {}.hasOwnProperty;
  for (var key in src) if (own.call(src, key)) obj[key] = src[key];
  return obj;
}

flagging shorthand {+x, -y}

$ coco -bce '{+x, -y}'
({
  x: true,
  y: false
});

ref. https://github.com/jashkenas/coffee-script/issues/885

accessor properties x:~ ->

$ coco -bcs
  a:~ (@a) ->     # setter if the function has one argument
  b:~      -> @b  # getter if none
  c:~  # indent and line them up to define both
    -> @c
    (@c) ->
  # uses `Object.defineProperty` when imported 
  import x:~ ->

({
  set a(a){
    this.a = a;
  },
  get b(){
    return this.b;
  },
  get c(){
    return this.c;
  },
  set c(c){
    this.c = c;
  }
});
Object.defineProperty(this, 'x', {
  get: function(){},
  configurable: true,
  enumerable: true
});

implicit array a = then b; c _

Some operators (: = := <<< <<<< return throw ! ~ delete typeof) can take a block on the right, which becomes an array when it contains multiple items.

$ coco -bcs
  list =
    1
    2
  atom =
    3

var list, atom;
list = [1, 2];
atom = 3;

listar * x, y

Leading star denotes an expression or array of expressions (thanks to implicit array).

$ coco -bcs
  points =
    * x:0 y:1
    * x:2 y:3

var points;
points = [
  {
    x: 0,
    y: 1
  }, {
    x: 2,
    y: 3
  }
];

$ coco -se
* * 0 1
  * 2

[ [ 0, 1 ], 2 ]

This is simply an alias to [] = => (empty assignment taking a block arrow). The last example is same as writing:

[] =
  [] =
    0, 1
  [] =
    2

numbers with variable radix 25rCoco

Borrowing from Smalltalk, numbers can have any base between 2 to 36 in the form RRrXXX.

$ coco -bce '[2r101010, 8r52, 36r16]'
[42, 42, 42];

number comment 42_000_ms

Underscores within numbers, and alphabets after decimals are ignored.

$ coco -pe '[0xDead_Beef 7_heads]'
[ 3735928559, 7 ]

character/number ranges \A to \C 1 til 10 by 2

Expands to comma separated characters or numbers.

$ coco -bce
  # syntax: CharOrNum ("to"|"til") CharOrNum ("by" Num)?
  [\A to \C]
  {98 to 97}
  f -3 til 4 by 2

["A", "B", "C"];
({
  98: 98,
  97: 97
});
f(-3, -1, 1, 3);

heregex dynamic flag //#x//?

$ coco -bcs
  //
    heregex
    #{ y } # normal interpolation
    #{ z } # sets the flag part
  //?

RegExp('heregex' + y, z);

regex string /\\/$

Same as .source, but on compile time. Useful for building large regexes piece by piece.

$ coco -bce '/\\/$; //\b#word\b//$'
'\\\\';
'\\b' + word + '\\b';

arguments shorthand @@

$ coco -bce '@@0 @@'
arguments[0](arguments);

Read: @rgument @rray

constructor shorthand .. _

$ coco -bce '@..classMethod ..'
this.constructor.classMethod(constructor);

empty splat [...]

... in an array desugars to ...[], which can be used to force implicit array or ignore intermediate items in destructuring.

$ coco -bcs
  empty =
    ...
  [first, ..., last] = array

var empty, first, last;
empty = [];
first = array[0], last = array[array.length - 1];

variable interpolation "#x"

Variables can be interpolated brace-free.

$ coco -bce '"(#id)"'
"(" + id + ")";
  • Includes special variables, namely this, @, &, and <>.

unjoined interpolation %"#x #y"

Prefixing % to an interpolated string gives you the raw parts as array.

$ coco -bce '%"#x #y"'
[x, " ", y];

Miscellaneous

automatic comma insertion [0 1]

Commas after non-callable literals can be omitted when spaced.

$ coco -bce '[null true 1 "2" [3] ->]'
[null, true, 1, "2", [3], function(){}];

automatic dot insertion @0\! _

Dots before {number,string,identifier}s can be omitted unless spaced.

$ coco -bce '@1"!"l'
this[1]["!"].l;

Similarly:

  • ? becomes ?. if surrounded without space.
    • a?b => a?.b
    • c?=b => c?.=d
  • ~, @ and @@ become .~/.@/.@@ when they follow previous token without space.

unspacing o\ p

\ cancels subsequent whitespaces a la Perl6.

$ coco -bcs
  o\ p  # triggers ADI
  f \
    x   # continues line and triggers implicit call
o.p;
f(x);

yada yada yada ...

Short for throw Error('unimplemented') on top level.

$ coco -e ...
Error: unimplemented

inline block comment f /* */ x

Works just as JS with no propagation to compilation.

$ coco -bce 'f /* implicit call */ x'
f(x);

Reference &

Special variable available in following constructions.

pipe |>

Binary operator that sets & to the left operand, then returns right.

$ coco -bce 'f x |> & + & |> g &'
var x$;
x$ = f(x);
x$ = x$ + x$;
g(x$);
  • Closes implicit call.
  • Has the lowest precedence, even lower than post-if etc.

map for xs => ...

Iterates over array-like target setting & to each value.

$ coco -pe 'for process.argv => ~&' 1 2

tap with x => ...

Sets & to target, runs block, then returns the target value.

$ coco -bcs
  # syntax: "with" Expression Block
  xhr = with new XMLHttpRequest
    &open \GET \data
    &send null

var xhr, x$;
xhr = (x$ = new XMLHttpRequest, x$.open('GET', 'data'), x$.send(null), x$);
  • Analogue to Common Lisp's anaphoric-prog1, Ruby's Object#tap, Io's Object do, etc.
cascade o => &p

The with keyword is optional on top level. This form requires at least one & within the block.

$ coco -bcs
  document.createElement \button
    &textContent = 'Run'
    &style
      &border-width = '3px'
      &font-weight  = 'bold'

var x$, y$;
x$ = document.createElement('button');
x$.textContent = 'Run';
y$ = x$.style;
y$.borderWidth = '3px';
y$.fontWeight = 'bold';

$ coco -e 'a => b'
SyntaxError: unreferred cascadee on line 1

CLI

compiled code in stack trace

$ coco ast
Failed at: ast.co
ReferenceError: typo is not defined
    at Object.<anonymous> (/usr/tmp/ast.co:248:1)
244|     }
245|   }
246|   return fromJSON;
247| }());
248+ typo;
249| Negatable = {
250|   show: function(){
251|     return this.negated && '!';
252|   },
    at Module._compile (module.js:402:26)
    at Object.run (/usr/local/lib/coco/lib/node.js:19:19)
    at compileScript (/usr/local/lib/coco/lib/command.js:172:14)
    at /usr/local/lib/coco/lib/command.js:130:18
    at /usr/local/lib/coco/lib/command.js:103:12
    at [object Object].<anonymous> (fs.js:107:5)
    at [object Object].emit (events.js:61:17)
    at afterRead (fs.js:878:12)
    at wrapper (fs.js:245:17)

interactive compilation

$ coco -icb
coco -cb> document.body
.........   &style.color = \purple
.........   &textContent = \rose
var x$;
x$ = document.body;
x$.style.color = 'purple';
x$.textContent = 'rose';
coco -cb>

JSON output

--json modifies the behavior of --ast and --compile.

$ coco -aje 3
{
  "type": "Block",
  "lines": [
    {
      "type": "Literal",
      "value": "3",
      "line": 1
    }
  ]
}

$ coco -cje '{6 9}'
{
  "6": 6,
  "9": 9
}
  • -aj: Serializes the AST in JSON.
  • -cj: Evaluates input, then prints the result in JSON.
Something went wrong with that request. Please try again.