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,
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.