(Sibilant: JavaScript with a LISP)

github.com/jbr/sibilant

Welcome!

Sibilant is an s-expression language that compiles to readable and idiomatic JavaScript. Sibilant is built on a simple macro system that gives you compile-time control over the output JavaScript as well as providing tools to smooth over some of JavaScript's historical idiosynchracies.

If you'd like to follow along in a local REPL,

bash> npm install -g sibilant
bash> sibilant
sibilant> (console.log "welcome to sibilant")
console.log("welcome to sibilant")
welcome to sibilant
      

All of the code in the left column for the rest of this document is editable and is compiled by your browser as you type.

var earth = {
  name: "earth",
  galaxy: "milkyWay",
  color: "blue"
};
var hello = (function hello$(planet) {
  /* hello examples/hello-world.sibilant:5:0 */

  var name = planet.name,
      galaxy = planet.galaxy;
  name = (typeof name !== "undefined") ? name : "planet";
  galaxy = (typeof galaxy !== "undefined") ? galaxy : "galaxy";
  return ("Hello, " + name + "! " + "How's the weather in the " + galaxy);
});
hello(earth);

Here's an quick sample of sibilant code (inspired by the first example on coffeescript.org).

var number = 42,
    opposite = true;
(function() {
  if (opposite) {
    return number = -42;
  }
}).call(this);
var square = (function square$(x) {
  /* square examples/welcome.sibilant:5:0 */

  return (x * x);
});
var list = [ 1, 2, 3, 4, 5 ];
var math = {
  root: Math.sqrt,
  square: square,
  cube: (function(x) {
    /* examples/welcome.sibilant:12:7 */
  
    return (x * square(x));
  })
};
var race = (function race$(winner, runners) {
  /* race examples/welcome.sibilant:15:0 */

  var runners = Array.prototype.slice.call(arguments, 1);

  return print(winner, runners);
});
(function() {
  if ((typeof elvis !== "undefined" && elvis !== null)) {
    return alert("I knew it!");
  }
}).call(this);

And here's the canonical node example from nodejs.org

require("http").createServer((function(req, res) {
  /* examples/nodejs.sibilant:2:18 */

  res.writeHead(200, { "Content-Type": "text/plain" });
  return res.end("Hello World\n");
})).listen(1337, "127.0.0.1")

Numbers and Strings

Numbers should look pretty familiar. Use commas to enhance readability.

[ 5, 10.5, -100.5, 10000000 ]

Strings are surrounded by double quotes. Multi-line strings are supported! Use a backslash to escape a double-quote inside of a string.

"this string is surrounded by\n" +
"\"double quotes\"\n" +
"and includes a line break"

Identifiers

Variable names may include letters, the period (.) and the hyphen (-). Lower case letters are idiomatic. They may end with a question mark (?) or with a bang (!). They may start with a sigil ($).

simple;
var multiWordVariableName = true;
isThisOn__QUERY(microphone);
save__BANG("with a bang");
var someJson = "{\"five\": 5}";
JSON.parse(someJson);
methodCall("withSingleQuoted", "strings");

Objects and Arrays

Objects and Arrays are supported as a proper subset of JSON.

{
  "string": "value",
  "number": 2,
  "array": [ 1, 2 ]
}

Idiomatic sibilant skips any unnecessary commas and colons that do not contribute to readability

{ color: "green" };
[ 1, 2, 3 ];
{
  key1: 5,
  key2: 10,
  key3: 15
};

Arrays and objects contents are accessed with the get and set macros

var object = {  },
    array = [ 1, 2, 3 ];
object.log = console.log;
object.log(array[1]);

Defining variables

Since sibilant is a tool for writing JavaScript, it exposes the var keyword with an identically named macro.

var variableName1 = "firstValue",
    variableName2 = "secondValue";

To modify an existing variable (assignment), use the assign macro.

a = 1;
b = 2;

Defining functions

Sibilant avoids hoisiting by defining functions as variable by default.

var alertHello = (function alertHello$(planetName) {
  /* alert-hello examples/def.sibilant:1:0 */

  var message = ("hello " + planetName);
  alert(message);
  return message;
});

If your function name includes a dot, it will not be defined as a variable.

var myObject = { value: 10 };
myObject.method = (function myObject$method$() {
  /* my-object.method examples/def-with-dot.sibilant:3:0 */

  return this.value = (this.value + 10);
});

Lambdas and Thunks

Lambdas are how sibilant defines anonymous functions.

fs.readFile("some/path", (function(error, content) {
  /* examples/lambda.sibilant:2:2 */

  return (function() {
    if (error) {
      return handleError(error);
    } else {
      return handleSuccess(content);
    }
  }).call(this);
}))

Because anonymous functions are is used so often in JavaScript, sibilant includes a shortcut to define lambdas, #.

array.map((function(item) {
  /* examples/undefined.sibilant:1:11 */

  return item.toUpperCase();
}))

Thunks are zero-arity lambdas (anonymous functions that accept no arguments). A shortcut for this is the #> macro.

$((function() {
  /* examples/thunk.sibilant:1:3 */

  return alert("loaded!");
}));
$("a#button", click((function() {
  /* examples/thunk.sibilant:3:21 */

  return alert("here");
})));

Conditionals

Sibilant provides two primary conditional macros: if and when. When is the simplest, only executing the subsequent block when the conditional evaluates to a truthy value. All conditionals in sibilant are expressions and evaluate to some value. This is done by introducing a scope with a self-executing function.

var currentUser = (function() {
  if (User.authenticated__QUERY()) {
    return User.findById(userId);
  }
}).call(this);

The `if` macro supports any number of branching paths. If the bodies of the branching paths needs more than one expression, the `do` macro is used.

(function() {
  if (Weather.raining__QUERY()) {
    ensure(Bike.fenders);
    return wear("raingear");
  } else if (Weather.sunny__QUERY()) {
    return wear("sunglasses");
  } else {
    return wear("random");
  }
}).call(this)

Iteration

Iteration is an example of a macro that can easily be redefined for different compilation contexts. Since the default compilation environments are modern browsers and node, the `each` macro compiles to `[].forEach`. If you needed to compile sibilant to an older JavaScript, the each macro could be easily modified to support a `for` loop.

[ 3, 1, 4, 1, 5, 9, 2, 6 ].forEach((function(number, digit) {
  /* examples/each.sibilant:1:0 */

  $("ol", ("<li>" + digit + " digit: " + number + "</li>").concat([]));
  return console.log("pi: a circular reference");
}))

Further Reading

For further documentation, check out docs.sibilant.org and ask questions on gitter or github issues.