Jisp is a programmable language that compiles to JavaScript.

It’s simpler and terser than JS, and has some additional powers. Its macro system lets you treat code as data and write functions that write code for you. Jisp’s extremely simple syntax protects against common JS pitfalls, and it abstracts away some legacy details, helping avoid errors and keep your code short.

See Why Jisp for a gist of why you may want to use it.

The current version of the jisp compiler is written in jisp. See the sourcecode on GitHub. See the issues section for known problems.

You can improve this documentation by sending pull requests to the gh-pages branch of the project repository.

Notes.

There's been a hiatus in updates. Mostly it's because I'm busy with other projects. Another part of the reason is that my views on JS have underwent a complete 180°. (Please mentally apply a vintage filter to everything else you read in this documentation.) JS doesn't need lispy features or macros because of its polymorphism and flexibility. Problems solved with macros are usually better solved with apt program architecture and using the language to its fullest. Preprocessors tend to be detrimental to the latter part.

When I finally get back to it, expect breaking changes. The next version (see the branch on Github) is a complete rewrite. It drops dead-end features and Ruby influences, and focuses on the core ideas. It's almost complete, so if you're interested in taking over, let me know.

All examples on this page are interactive: edit the jisp to see changes. The JavaScript snippets are compiled in your browser. If you don't see them, make sure JS is enabled and refresh the page.

Interactive Playground

This example defines a macro (an instruction for the compiler) and uses it to simplify the subsequent gulp task definitions.

gulp.task('js', (function() {
  return handle(gulp.src(jsFiles))
    .pipe(handle(conc('deps.js')))
    .pipe(handle(uglify({
      mangle: false
    })))
    .pipe(handle(gulp.dest('public/js/tmp/')));
}));
gulp.task('jisp', (function() {
  return handle(gulp.src(jispFiles))
    .pipe(handle(conc('app.js')))
    .pipe(handle(jisp()))
    .pipe(handle(uglify({
      mangle: false
    })))
    .pipe(handle(gulp.dest('public/js/tmp/')));
}));

Example of dynamically constructing code with a macro.

Installation and Usage

Get Node.js. This will give you the local node runtime and the npm package manager. Install jisp with npm:

$ npm install -g jisp

Alternatively, download the source, run npm install to get the dependencies, and use ./bin/jisp and ./jisp/jisp.js as entry points.

Require in Node, registering the file extension:

require('jisp/register');

This allows you to require jisp scripts directly from your code, like so:

require('./app.jisp');

Launch an interactive REPL:

$ jisp
jisp>

Compile a file or directory:

$ jisp -c <file>

Stream-compile with gulp-jisp.

While not recommended for production, jisp can be directly used in the browser. Include the browser/jisp.js file with your §page. It registers the text/jisp script type to automatically compile and run jisp scripts loaded with src or included in script tags. It also exposes a global object with the compile and eval methods for jisp code. This is how this documentation is implemented.

Super basic Sublime Text build system (OS X):

  • sudo npm install -g jisp
  • Tools > Build System > New Build System
  • put lines:

    {
      "cmd": ["jisp", "$file"],
      "path": "/usr/local/bin/"
    }
    
  • save to: ~/Library/Application Support/Sublime Text 3/Packages/User

Code Structure

Jisp code consists of nested arrays. To be easier on the eyes, they’re delimited with parentheses () instead of brackets. Individual elements are separated by whitespace rather than commas. This documentation refers to them as lists, but it’s just arrays.

var myVar;
console.log('Fire in the house!');
[1, NaN, 'bison'];
[];
myVar = 'myValue';
(function(x) {
  return alert(x);
});

Object literals are also delimited with parentheses, elements separated by whitespace rather than commas. This documentation often refers to object literals as hash tables to discern them from objects in general.

({
  place: 'tavern',
  drink: 'rum'
});
({});
({
  using: 'the',
  hash: 'macro'
});

The built-in hash macros : and :: give you easy ways to build hash tables as lists. : with two or more arguments runs purely at compile time; other forms have runtime components. See the built-ins section for details.

Identifiers and literals without parentheses are called atoms:

Infinity;
'February';
'split across\ntwo lines';
/!@#$%/;
parseInt;
null;
undefined;
true;

Strings in jisp are multiline.

Parentheses in the jisp code are not cosmetic. Your code consists of arrays. This allows to easily deconstruct and construct it when using macros. We’ll get to this in the Macros section.

Jisp is mostly whitespace-agnostic. Elements in lists and hash tables are separated by whitespace, but other than that, jisp is completely insensitive to whitespace and indentation, relying on parentheses as delimiters. Forms can contain linebreaks.

In a non-quoted list, the first element is considered a function, and the list compiles as a function call:

alert('A dragon cometh!');
isNaN(parseInt(Infinity));

In fact, even operators are considered functions and use the prefix notation. This allows them to take multiple arguments:

2 & 3 & 4;
'using jisp' + 'you’re' + 'awesome';
4 * 5 * (3 - 6 - 10) * (1 ^ 5);

[NYI] Planned feature: you’ll be able to pass operators around like any other function:

var x;
x = [1, 2, 3, 4];
x.sort( < );

Not all lists are function calls. Some are special forms with their own resolution rules. This includes assignment, quoting, function definitions, conditionals, loops, and JS keywords.

Everything an Expression

Most languages distinguish statements and expressions. An expression is a piece of code with a value, something you can assign or return from a function. Statements have side effects but no value. In jisp, everything is an expression. It helps you write functional code, avoiding unnecessary data fiddling.

An atom such as a number literal, a string literal, or identifier, resolves to itself.

A hash table resolves to itself, with its values resolved individually.

Lists resolve by special rules:

  • An unquoted empty list () (not `()) resolves to nothing.
  • Special forms starting with a keyword like =, def, and others, resolve by their own special rules.
  • Lists starting with a macro name resolve as macro calls at compile time.
  • Everything else resolves as a function call.

Examples:

var x;
x = 10;
(function(x) {
  return (x / 10);
});
false ? 1 / 0 : 4 ^ 6;
(function(y) {
  return (10 + y);
})(13);

Inside a nested form, forms are resolved left-to-right, inner-to-outer, just like you’d expect.

Quoting

If lists resolve as function calls, how do you write array literals? The answer is quoting. The special form (quote x) prevents the list x from being resolved, letting it remain an array. Prepending a list with ` is shorthand syntax:

1(2, 3);
[1, 2, 3];
[1, 2, 3];

Directly quoting a non-primitive atom (name or string) stringifies it:

"myvar";
"'mystring'";

Which is convenient for macros. Atoms in macro call arguments are quoted implicitly.

Quoting implicitly propagates to inner lists:

(2 ^ 1) + ('π' === 'Ω');
[+, [ ^ , 2, 1],
  [is, 'π', 'Ω']
];

To let an element resolve, unquote it with a comma ,:

[
  [ * , 2, 3],
  [is, true, false]
];
[2 * 3, true === false];

Aside from writing array literals, quoting and unquoting is primarily used in macros. See the Macros section below.

Blocks and Chaining

Because jisp code consists of nested expressions, even a multi-line block must be a single expression. How to make one? By wrapping several forms in the special form do:

function elongate(str) {
  str += str;
  console.log('duplicated:', str);
  return str;
}
elongate;

On compile, a jisp file is implicitly wrapped in a top-level do form.

do resolves to the value of its last form. It’s a requirement for chaining methods:

var bugs, str, grated;
bugs = [];
bugs.push('butterfree');
bugs.push('beedrill');
console.log(bugs);
str = ',…x';
grated = str
  .replace(/…/g, ' … ')
  .replace(/,/g, ' , ')
  .trim();

Note: do is the only jisp expression that can consist of multiple forms. The body of each function, loop, etc. is always a single form, and requires a do to include multiple expressions.

Object Properties

As you’d expect, object properties are referenced with the .dot or [bracket] notation:

var bunny;
bunny = {
  bouncy: true,
  fluffy: false
};
bunny.bouncy;
bunny['fluffy'];

Dot and bracket notation is a syntax shortcut to getting a property of an object. Internally, jisp uses the (get obj prop) special form:

bunny[bouncy];
bunny['fluffy'];

Quite naturally, you can access properties of resolved forms:

String(113).length;
String(113)[1 + 1];

But you can also access it with the get form, and do for chaining (below). This is useful in macros when you pass around names of objects and properties individually.

String(113)['length'];
String(113)[1 + 1];

To chain methods, wrap them in a do form:

cartoonSnake
  .crawlForward(10)
  .turnLeft()['crawlForward'](5)
  .eat('bunny')
  .elongate(food.weight);

Alternatively (even though it’s kinda gross), you can do it like this:

cartoonSnake.crawlForward(10).turnLeft()['crawlForward'](5).eat('bunny').elongate(food.weight);

Functions

Jisp mirrors JavaScript function facilities 1-1 and adds some more.

Definition

Named function:

(def <name> [<... params>] [<body>])
function fact(x) {
  return ((x === 0) ? 1 : (x * fact(x - 1)));
}
fact;

Anonymous function (sometimes called lambda):

(fn [<... params>] [<body>])
(function(first, second) {
  return (first + second);
});
(function() {
  return returnMe;
});
(function() {});

Calling and Returning

A function call is a list starting with the function’s name or with a form that resolves to a function:

var ringMyBell;
ringMyBell = (function(bell) {
  return console.log(bell);
});
ringMyBell('\x07');
(function(x) {
  return ('Hello ' + x);
})('World!');

A function returns the resolved value of its body. You almost never need to return values manually:

function numerify(x) {
  var _ref;
  if (isNaN(Number(x))) {
    console.log('not a number:', x);
    _ref = NaN;
  } else {
    _ref = Number(x);
  }
  return _ref;
}
numerify;

It’s often best to keep each function’s body a single conditional tree with branches ending in return values.

Inside a function’s body, # (NYI) is an array of the arguments passed to it, and #n refers to an individual argument by order.

(function() {
  return (arguments[0] * arguments[2]);
})(3, 100, 4);

As a side effect of implicit returns, when making a prototype constructor, you need to end it with this as the return value to make the new declarations work.

Lambda Syntax

Because functions are so cheap in JavaScript, jisp comes with a shorthand syntax for anonymous functions:

{<body>}
(function() {
  return alert('It’s a dragon!');
});
(function() {
  return (3 + 4);
});
(function() {});

This goes hand in hand with the # notation:

var array;
array = [0, 1, 2, 3];
(function() {
  return (arguments[0] * arguments[1]);
});
array.filter((function() {
  return (arguments[0] > 1);
}));

Let

let is a short way to declare variables in an isolated scope and run that scope. It resolves to the value returned by its body.

(let [var value [var value ...]] [<body>])
(function(health) {
  return console.log(health);
})(100);
typeof health !== 'undefined';

Just like assignment, it takes variables in pairs:

(function(plus, minus) {
  return console.log(plus ^ minus);
})(110, -12);
(typeof plus !== 'undefined') || (typeof minus !== 'undefined');

let is currently implemented as a self-executing anonymous function. In the future editions of EcmaScript, it will be changed to use the native let statement with a block.

Assignment

Like all other forms, assignment uses the prefix notation:

(= var value [... var value])
(= var)
var newvar, pi;
newvar = 'some value';
pi = ((2 === 3) ? NaN : Math.PI);

All assignments in jisp (not just =) automatically hoist var declarations, saving you keystrokes and safeguarding against leaking globals. Variables are only declared if not already in scope. To shadow an outer variable on purpose, use let.

Like many other forms, = takes multiple arguments. It assigns them in pairs. Its resolved value is the last assignment:

var first, second, third, lastest;
first = 'Coffee';
second = 'Beer';
lastest = (third = 'Milk');

The right hand of an assignment can be an arbitrary form, even a block:

var shifty, picky, dodgy, x, _ref, _err;
shifty = null;
picky = (false ? 'nose up' : 'smile');
try {
  _ref = something;
} catch (_err) {
  _ref = 'unsuccessful';
}
x = (dodgy = _ref);

Calling = with a single argument merely declares that name if not already in scope:

var emptyVar;
emptyVar;

Destructuring Assignment

Assign to a list of identifiers to take apart the right hand side of the assignment and bind its parts:

(= (var0 [... varN]) value)
var smaller, bigger, _ref, _i;
_ref = [3, Infinity];
smaller = _ref[0];
bigger = _ref[1];

This assignment is positional: [0] [1] and so on. To collect all remaining parts into an element, prefix it with ... or :

var first, mid, closing, _ref, _i;
_ref = [4, 8, 0, 3];
first = _ref[0];
var mid = 3 <= _ref.length ? [].slice.call(_ref, 1, _i = _ref.length - 1) : (_i = 1, []);
closing = _ref[_i++];
mid;

...x and …x is a shortcut to the (spread x) special form, which you can, and sometimes need to, use directly. Spreading is moderately greedy: it takes as many elements as possible, but has lower priority than non-spread identifiers.

Spreading and Rest Parameter

Borrowed straight from the upcoming EcmaScript 6 specification.

Spread Into List

In a list, prefix elements with ... or to spread their elements into the list, flattening it:

[1, 2, [3, 4],
  [5, 6]
];
[].concat([1, 2]).concat([3, 4]).concat([5, 6]);

Argument Spread

Spread a list into a function call to pass its elements as individual arguments:

var pair;
pair = ['dog', 'lizard'];
console.log.apply(console, [].concat(['cat']).concat(pair));

Rest Parameter

Prefix a parameter with ... or to make it a rest parameter that collects the remaining arguments into a list. This works the same way as destructuring assignment:

function categorise(quick) {
  var slow, _i;
  var moderate = 3 <= arguments.length ? [].slice.call(arguments, 1, _i = arguments.length - 1) : (_i = 1, []);
  slow = arguments[_i++];
  return console.log('(' + moderate.join(' ') + ')');
}
categorise;
categorise('hare', 'turtle', 'human', 'snail');

Conditionals

Jisp improves the JavaScript conditionals and gives you some new ones. It’s also trivial to define your own conditionals with macros.

is      ; equality test
isnt    ; inequality test
not     ; negation
or      ; ||
and     ; &&
in      ; value in iterable
of      ; property of object
isa     ; positive type check
isnta   ; negative type check
?       ; existence check
?!      ; nonexistence check
any     ; picks first existing value, if any
if      ; if
switch  ; switch

Logic

is is the equality test. With a single argument, it checks its truthiness by double negation. With two or more arguments, it checks if the first equals any of the others by a combination of === and ||:

(is <name>)
(is <name> <something>)
(is <name> <something> [<other> ...])
!!true;
grass === 'green';
she === 'smart' || she === 'beautiful' || she === 'artistic';

isnt is the inverted form of is which also takes multiple arguments:

(isnt <name>)
(isnt <name> <something>)
(isnt <name> <something> [<other> ...])
!false;
fire !== wet;
she !== 'grumpy' && she !== 'magpie' && she !== 'far away';

Logical or is or and logical and is and. Like many other forms, they take multiple arguments:

(or  [<a> [<b> ...]])
(and [<a> [<b> ...]])
NaN || Infinity || "myvar";
true && 'sun is hot' && (2 < 3);

Check if a value is in an iterable (array or string) with in:

(in <value> <iterable>)
var bush;
bush = 'woods';
[].indexOf.call(['forest', 'woods', 'thicket'], bush) >= 0;
[].indexOf.call('mystring', 's') >= 0;

Check if an object has a property with of:

(of <property> <object>)
var snake;
snake = {
  venom: true,
  fangs: true,
  talons: false
};
'venom' in snake;

isa is a short way to test an object’s type. It takes multiple arguments and returns true if any match:

(isa <name> <type> [<type> ...])
typeof Math.PI === 'number';
typeof null === 'number' || typeof null === 'boolean';

isnta is the negated version of isa. It takes multiple arguments and returns true if none match:

(isnta <name> <type> [<type> ...])
typeof null !== 'function';
typeof 'Sun' !== 'number' && typeof 'Sun' !== 'string';

Existence

Jisp provides three powerful existence macros: ?, ?!, and any.

? is the ultimate existence checker. This macro takes any number of arguments and resolves to true if any of them exist (are defined) and to false otherwise.

(? <name>)
(? <name> [<name> ...])
(? <object.property> [<name> ...])
var elephants;
elephants = 'exist';
typeof dinosaurs !== 'undefined';
(typeof mammoths !== 'undefined') || (typeof elephants !== 'undefined');

It’s smart about properties: it takes property references apart and checks them in order, starting with the base object, letting you pinpoint the existence of a property with just one conditional:

(typeof object !== 'undefined') && (typeof object.property !== 'undefined') && (typeof object.property[0] !== 'undefined') && (typeof object.property[0]['method'] !== 'undefined');

?! is the negated version of ? with the exact same qualities:

(?! <name>)
(?! <name> [<name> ...])
(?! <object.property> [<name> ...])
(typeof myVar === 'undefined') && (typeof null === 'undefined');
(typeof obj === 'undefined') || (typeof obj.prop === 'undefined') || (typeof obj.prop[0] === 'undefined') || (typeof obj.prop[0]['method'] === 'undefined');

any is a sliding switch that combines or and ?: it resolves to the first value that exists and is truthy, or just the last value that exists:

(any <name> [<name> ...])
((typeof NaN !== 'undefined') && NaN) || ((typeof Infinity !== 'undefined') && Infinity);
((typeof false !== 'undefined') && false) || ((typeof 0 !== 'undefined') && 0) || (((typeof obj !== 'undefined') && (typeof obj.prop !== 'undefined') && (typeof obj.prop[0] !== 'undefined')) && obj.prop[0]) || (((typeof Math !== 'undefined') && (typeof Math.PI !== 'undefined')) && Math.PI);

Single conditional and no runtime error.

If

Like everything in jisp, if is a single form that resolves to a value. When possible, it compiles into the ternary or binary form. You can assign an if to a variable or return it from a function.

(if <test> <then> [<elif test then> ...] <else>)
(if <test> <then> <else>)
(if <test> <then>)
var beast;
if (true) console.log('breaking off');
!!'universe expanding' ? console.log('flight normal'): alert('catastrophe');
if (hunting) {
  beast = randomBeast();
  shoot(beast);
} else {
  cook('meat');
}

Like everything else, the block form resolves to a value that can be assigned or returned:

function truthiness(x) {
  var _ref;
  if (x) {
    console.log('truthy');
    _ref = x;
  } else {
    console.log('falsy');
    _ref = false;
  }
  return _ref;
}
truthiness(Infinity);

Else-ifs are special forms inside the if expression. The last non-elif expression is taken as the else-branch (undefined if omitted).

(elif <test> <branch>)
if (hungry) {
  eat();
} else if (thirsty) {
  drink();
} else if (tired) {
  sleep();
} else {
  write(code);
}

Switch

A switch form automatically inserts break; statements, protecting you from accidental fall-through:

(switch <predicate> [<case test body> ...] <default>)
var x;
x = 0;
switch (x) {
  case -1:
    'negative one';
    break;
  case 0:
    'zero-ish';
    break;
  default:
    NaN;
}

Quite naturally, switch is also an expression that resolves to a value:

var _ref;
switch (Math.PI) {
  case 1:
    _ref = 'welcome to Lineland';
    break;
  case 2:
    _ref = 'welcome to Flatland';
    break;
  case 3:
    _ref = 'welcome to ancient Egypt';
    break;
  default:
    _ref = 'world still spinning';
}
console.log(_ref);

Try / Catch

In jisp, even try is an expression. Use it like so:

(try <try> (catch err <catch>) <finally>)
(try <try> (catch err <catch>))
(try <try> <catch> <finally>)
(try <try> <catch>)
(try <try>)
var _ref, _err;
try {
  _ref = jump('10 meters high');
} catch (_err) {
  _ref = undefined;
}
console.log(_ref);
try {
  eat('a kilogram of sushi');
} catch (err) {
  console.log(err);
} finally {
  'But I’m happy anyway';
}

More conditionals coming up. The macro system also makes it trivial to define your own conditionals with arbitrary syntax.

Loops

Jisp abstracts away the legacy details of JavaScript loops and makes them a lot more expressive. It comes with three loops: for, over, and while.

Over

The over loop iterates over values and keys of any object. It also accesses inherited properties and custom prototype methods.

(over [<value> [<key>]] <iterable> <body>)
var animals, key, val, _ref;
animals = {
  squirrel: 'Eevee',
  fox: 'Vulpix'
};
_ref = animals;
for (key in _ref) {
  val = _ref[key];
  console.log(key, val);
}
'';

over automatically builds a list of values from each iteration. This list is its resolved value:

var cats, name, catnames, colour, bigcolours, _key, _res, _ref, _res0, _ref0;
cats = {
  pink: 'Persian',
  yellow: 'Skitty'
};
_res = [];
_ref = cats;
for (_key in _ref) {
  name = _ref[_key];
  if (typeof name !== 'undefined') _res.push(name);
}
catnames = _res;
_res0 = [];
_ref0 = cats;
for (colour in _ref0) {
  name = _ref0[colour];
  if (typeof colour.toUpperCase() !== 'undefined') _res0.push(colour.toUpperCase());
}
bigcolours = _res0;

Iteration only collects results that are not undefined, so you can easily filter them:

var cats, cat, mCats, _key, _res, _ref, _ref0;
cats = {
  pink: 'Mew',
  yellow: 'Meowth',
  white: 'Absol'
};
_res = [];
_ref = cats;
for (_key in _ref) {
  cat = _ref[_key];
  if (typeof(_ref0 = ((cat[0] === 'M') ? cat : undefined)) !== 'undefined') _res.push(_ref0);
}
mCats = _res;

For

When iterating over arrays and strings, you usually want to hit all elements in order and don’t want extra properties tagging along. In those cases, use the for loop:

(for [<value> [<index>]] <iterable> <body>)
var index, char, _ref;
_ref = 'meow';
for (index = 0; index < _ref.length; ++index) {
  char = _ref[index];
  console.log(index, char);
}
'';

It resolves to a list of values from each iteration. Just like over, it filters them by undefined:

var array, x, _i, _res, _ref, _i0, _ref0;
array = [
  ['drink', 'milk'],
  ['sweet', 'icecream'],
  ['drink', 'coffee']
];
console.log('-- all:');
_res = [];
_ref = array;
for (_i = 0; _i < _ref.length; ++_i) {
  x = _ref[_i];
  if (typeof x !== 'undefined') _res.push(x);
}
console.log(_res);
console.log('-- only drinks:');
_ref0 = array;
for (_i0 = 0; _i0 < _ref0.length; ++_i0) {
  x = _ref0[_i0];
  if ((x[0] === 'drink')) x;
}

[NYI]: planned feature. If the iterable is an integer larger than 0, jisp will substitute it for a range starting at 1, making for a repeat-N loop:

(= warcry '')
(for 5 (+= warcry 'waagh! '))
warcry
; waagh! waagh! waagh! waagh! waagh!

While

For finer-grained control, use the while loop. It works like in JavaScript, but like everything in jisp, it’s an expression. By default, it resolves to a list of values from each iteration. Like for and over, it filters them by undefined, allowing you to skip values:

(while <test> <body>)
var bugs, array, x, even, _res, _ref, _res0, _ref0;
bugs = ['missing comma', 'missing semicolon'];
_res = [];
while (bugs.length > 0) {
  if (typeof(_ref = (bugs.shift() + ' avoided')) !== 'undefined') _res.push(_ref);
}
console.log(_res);
array = [0, 1, 2, 3, 4];
_res0 = [];
while (array.length > 0) {
  x = array.pop();
  if (typeof(_ref0 = (((x % 2) === 0) ? x : undefined)) !== 'undefined') _res0.push(_ref0);
}
even = _res0;

You can also order a final resolved value:

(while <test> <body> <return-value>)
var beers, drunk;
beers = 0;

function sober() {
  return (beers < 10);
}
sober;
while (sober()) {
  ++beers;
}
drunk = ('drunk after ' + beers + ' beers');

Comprehensions

Other languages typically devise special syntax for list comprehensions (a set builder notation). In jisp, you get the same functionality just by combining its basic features.

range is a trivial built-in function that returns a list from N to M:

function range(start, end) {
  var a, _res, _ref;
  if ((typeof end === 'undefined')) {
    end = start;
    start = 0;
  }
  _res = [];
  while (true) {
    if ((start <= end)) {
      a = start;
      ++start;
      _ref = a;
    } else {
      _ref = undefined;
      break;
    }
    if (typeof _ref !== 'undefined') _res.push(_ref);
  }
  return _res;
}
range(0, 5);

for and while (see Loops) are list-building expressions and can be combined with range, and optionally if, to make a comprehension:

function range(start, end) {
  var a, _res, _ref;
  if ((typeof end === 'undefined')) {
    end = start;
    start = 0;
  }
  _res = [];
  while (true) {
    if ((start <= end)) {
      a = start;
      ++start;
      _ref = a;
    } else {
      _ref = undefined;
      break;
    }
    if (typeof _ref !== 'undefined') _res.push(_ref);
  }
  return _res;
}
var x, _i, _res, _ref, _ref0, _i0, _ref1;
_res = [];
_ref = range(0, 6);
for (_i = 0; _i < _ref.length; ++_i) {
  x = _ref[_i];
  if (typeof(_ref0 = (x * x)) !== 'undefined') _res.push(_ref0);
}
console.log(_res);
_ref1 = range(0, 6);
for (_i0 = 0; _i0 < _ref1.length; ++_i0) {
  x = _ref1[_i0];
  if (((x % 2) === 0)) x * x;
}

Macros

Macros are compile-time functions that generate code. A macro takes code as input and returns code that’s put in its place. At compile, macro definitions are yanked from your code, then macros are recursively expanded. After all macros are expanded, jisp is compiled to JS:

Definition:
(mac name [<params>] <body>)

Call:
(<name> [<code>])
((typeof NaN !== 'undefined') && NaN) || ((typeof Infinity !== 'undefined') && Infinity) || ((typeof myVar !== 'undefined') && myVar);

Most of jisp.jisp is written with macros.

The lifetime of your code without macros:

code -> compile into JS -> execute

The lifetime with macros:

code => parse macros <-> expand macros -> compile into JS => execute

It seems to be a trend among modern languages to introduce limited macro support in form of templates. Jisp brings macros to JavaScript but it’s not limited to templating. Macros are complete, real, custom functions using the full power of the language to run arbitrary logic and transform code in arbitrary ways.

Templating

Templating is the most basic use. Let’s make a macro that generates named function definitions:

Prefix code to return with `:
`(<code>)

Unquote elements with , to resolve (transclude) them during macro call:
`(<code> ,<elem> <code>)
function mul() {
  var _i;
  var args = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  return ((args.length !== 0) ? args.reduce((function() {
    return (arguments[0] * arguments[1]);
  })) : undefined);
}
mul;

In this example, the macro returns the form starting with def. Quoting with ` prevents this form from resolving during the macro call and lets the macro return it as code. Unquoting the macro arguments name and operator by prepending them with , transcludes them into the template. Try adding your own macro calls to generate new definitions.

Because macros are real functions, you can edit the return code in arbitrary ways. For instance, based on the arguments passed. Let’s make our operator macro slightly more versatile:

function add() {
  var _i;
  var args = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  return ((args.length === 0) ? undefined : args.reduce((function() {
    return (arguments[0] + arguments[1]);
  })));
}
add;

function div() {
  var _i;
  var args = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  args.unshift(1);
  return ((args.length === 0) ? 1 : args.reduce((function() {
    return (arguments[0] / arguments[1]);
  })));
}
div;

In this example, the logic (args.unshift ,zeroValue) is only included if a zeroValue was passed to the macro. Run the resulting functions with none and one argument to see the difference.

Code Construction

Because jisp code is a series of nested arrays, macros can deconstruct and construct it on the fly. This is why we have those parentheses.

As a silly example, you could enable reverse syntax by reversing the code passed to a macro:

console.log([] + 'hello ' + 'world');

But let’s get more serious. Getting back to the example at the top of the page. Suppose you’re writing a gulp config file full of repetitive blocks like these:

gulp.task('jisp', function() {
  return handle(gulp.src(jispFiles))
  .pipe(handle(concat('app.js')))
  .pipe(handle(jisp()))
  .pipe(handle(uglify({mangle: false})))
  .pipe(handle(gulp.dest('public/js/tmp/')));
});

You can’t deduplicate this with functional abstractions alone, and are forced to write this repetitive code by hand. But you can abstract it away with a macro:

gulp.task('jisp', (function() {
  return handle(gulp.src(jispFiles))
    .pipe(handle(conc('app.js')))
    .pipe(handle(jisp()))
    .pipe(handle(uglify({
      mangle: false
    })))
    .pipe(handle(gulp.dest('public/js/tmp/')));
}));

What happened? The macro takes its arguments as an array, takes it apart in pairs, and constructs a new array of the resulting code, filling in the repetitive blocks we wanted to dedup. The constructed code in this example is:

(gulp.task 'jisp' (fn (do
  (handle (gulp.src jispFiles))
  (.pipe (handle (conc 'app.js')))
  (.pipe (handle (jisp)))
  (.pipe (handle (uglify (mangle: no))))
  (.pipe (handle (gulp.dest 'public/js/tmp/'))))))

And it replaces the macro call before the code is compiled.

We’ve just enabled a new shorter, cleaner syntax for the rest of our configuration file, and deduplicated our code in a way not possible with plain JavaScript. It should be noted that macros take any kind of input; it could be hash tables or bigger blocks of code. See jisp.jisp for bigger examples.

Macros can have arbitrary symbols and even literal strings as names. Suppose you’re writing a lot of prototype extenders and want to shorten the definitions. In other languages, you’re lucky if you have special syntax for that. In jisp, make the syntax yourself:

Plant["prototype"]["grow"] = (function(time, speed) {
  return (this.length += (time * speed));
});
Animal["prototype"]["growl"] = (function(decibels) {
  return (this.loudness = decibels);
});

Sometimes you want the code returned from a macro to contain new variable binginds. Prefix a name with # to make it a service name that is guaranteed to be unique in the current scope. If it clashes with any other variable, it will be renamed to avoid the conflict.

var uniq, uniq0;
uniq0 = 'my unique variable';
uniq = 'declared outside macro';

Finally, macros can self-expand on definition:

process.stdout.write('hello world');
process.stdout.write('another call');
99 + 44 + 11;
Infinity + -Infinity;

Macro Import and Export

Macros can be imported in three ways:

  • compile or require a macro-containing file before others within the same Node runtime or on the same browser page;
  • use the importMacros method of the object exported by the compiler or the global jisp object in the browser;
  • access the macros store exported by the compiler.

Macros are kept in the macros object that exists during the compiler runtime. It’s exposed in the jisp object and can be accessed and modified directly. The recommended way to import macros is by calling the importMacros method that takes one or more macro stores and merges them into the macro object, overriding the existing macros. Each store is a hash table where keys are macro names and values are macro functions.

(= myStore (testMacro: (fn `nameToReturn)))

(= jisp (require 'jisp'))

(jisp.importMacros myStore)

(testMacro)  ; replaced by `nameToReturn`

The macros object persists between compiler calls. If you’re using a build script that compiles multiple jisp files within the same runtime, you can simply put macros in a file and require or compile it before others. This also works when running jisp scripts directly with require.

When a macro is referenced in code, it’s embedded at the top of your program and can be assigned and exported from a module. See macros.jisp for an example.

Notes

After each macro expansion, the new code is recursively checked for macro definitions and calls. This allows macros to be nested, and even contain new macro definitions. See jisp.jisp for examples; most of it is written with nested macros.

To avoid confusing macros for functions, it’s good style to begin their names with mac.

It’s important to realise that macros are compile-time, not run-time. They live in the land of names, not in the land of values like functions. Rather than passing values by names to macros, you pass names, or code in general. A macro doesn’t give a flying duck about scope or variable bindings. You aren’t constrained by scope or object reference issues, and don’t have to pass around objects you want to access. You just construct the code you want, where you want it, at compile time.

Built-ins and Embedding

Jisp comes with some built-in macros and functions, and faculties for importing them and embedding into compiled programs.

Macros

Most built-in macros are conditionals. See the conditionals section. Some are property accessors. prn is a syntax-level alias for console.log.

x[0];
x[0];
x["slice"](1);
x["slice"](1);
x["slice"](0, -1);
x["slice"](-1)[0];
(function(x) {
  return (x * 2);
})(10);
typeof x === 'type';
typeof x !== 'type';
(typeof x !== 'undefined') && (typeof x.y !== 'undefined');
(typeof x === 'undefined') || (typeof x.y === 'undefined');
((typeof x !== 'undefined') && x) || ((typeof y !== 'undefined') && y);
console.log(x, y);

: is a hash table builder. When given multiple arguments, it takes them as key-value pairs and compiles to an object literal:

({
  basic: 'hash',
  table: 'syntax'
});

This has its use in macros (at compile time). To use the hash builder at runtime, call it with a single argument:

var calc, _res, _ref;
calc = ['number', Math.PI, 'professor', 'Archimedes'];
_res = {};
_ref = calc;
while (_ref.length > 0) {
  _res[_ref.shift()] = _ref.shift();
}
JSON.stringify(_res);

Take note that : is destructive. (: ( .slice)) your lists if you want to keep originals.

:: is a concatenating hash builder. It concatenates its arguments, flattening them if they’re arrays, and passes the result to :.

function concat() {
  var _res, lst, _i, _i0, _ref;
  var lists = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  _res = [];
  _ref = lists;
  for (_i0 = 0; _i0 < _ref.length; ++_i0) {
    lst = _ref[_i0];
    _res = _res.concat(lst);
  }
  return _res;
}
var _res, _ref;
_res = {};
_ref = concat.apply(concat, [].concat(['first', 'pair']).concat(['second', 'pair']));
while (_ref.length > 0) {
  _res[_ref.shift()] = _ref.shift();
}
JSON.stringify(_res);

:: is particularly useful for building hashes in loops.

function concat() {
  var _res, lst, _i, _i0, _ref;
  var lists = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  _res = [];
  _ref = lists;
  for (_i0 = 0; _i0 < _ref.length; ++_i0) {
    lst = _ref[_i0];
    _res = _res.concat(lst);
  }
  return _res;
}

function duplicate() {
  var arg, _i, _res, _i0, _res0, _ref, _ref0, _ref1;
  var args = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  _res = {};
  _res0 = [];
  _ref = args;
  for (_i0 = 0; _i0 < _ref.length; ++_i0) {
    arg = _ref[_i0];
    if (typeof(_ref0 = [arg, arg]) !== 'undefined') _res0.push(_ref0);
  }
  _ref1 = concat.apply(concat, [].concat(_res0));
  while (_ref1.length > 0) {
    _res[_ref1.shift()] = _ref1.shift();
  }
  return _res;
}
duplicate;
JSON.stringify(duplicate('troubles', 'happiness'));

Functions

Jisp has a special faculty for adding global functions to the language. If any of them is referenced in code, it’s embedded at the top of your program on compile. No globals are leaked. If the function is reassigned before being referenced, it’s not embedded. Like with macros, jisp provides a way to import these functions, extending the language. It also comes with a few:

list is a list (array) builder. It’s roughly equivalent to (Array x), but also accepts 0 or 1 arguments.

(list [<args> ...])
function list() {
  var _i;
  var args = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  return [].concat(args);
}
list();
list('wizard', 'hat', 'staff');

concat is like list except it flattens lists passed as arguments, concatenating them:

(concat [<args> ...])
function concat() {
  var _res, lst, _i, _i0, _ref;
  var lists = 1 <= arguments.length ? [].slice.call(arguments, 0, _i = arguments.length - 0) : (_i = 0, []);
  _res = [];
  _ref = lists;
  for (_i0 = 0; _i0 < _ref.length; ++_i0) {
    lst = _ref[_i0];
    _res = _res.concat(lst);
  }
  return _res;
}
concat([true, false], [NaN], Infinity);

range is a function that builds a list from N to M. It’s used in comprehensions:

(range [start] end)  ; default start 0
function range(start, end) {
  var a, _res, _ref;
  if ((typeof end === 'undefined')) {
    end = start;
    start = 0;
  }
  _res = [];
  while (true) {
    if ((start <= end)) {
      a = start;
      ++start;
      _ref = a;
    } else {
      _ref = undefined;
      break;
    }
    if (typeof _ref !== 'undefined') _res.push(_ref);
  }
  return _res;
}
range(-1, 6);

Function Import and Export

Similarly to macros, functions can be imported in two ways:

  • use the importFunctions method of the object imported with require or the global jisp object in the browser;
  • directly access and modify the function store exposed by the module.

The global functions are stored in the functions object that exists during the compiler runtime. Unlike macros, compiling a file doesn’t affect the function store. You need to import them like so:

(= myFuncs (sqr:  (fn x (* x x))
            cube: (def cube x (* x x x))))

(= jisp (require 'jisp'))

(jisp.importFunctions myFuncs)

sqr      ; function embed
(cube 3) ; function embed, 27

Try this in a REPL or a file to see how functions are embedded after importing. This faculty makes it easy to extend the language in a modular way with zero global leaks and zero global dependency.

Style

Jisp is insensitive to whitespace, but humans don’t read code by counting parens; we read it by indentation. Your indentation should reflect the nesting of expressions, branches of execution. Parallel branches share the same indent, nested branches are indented further.

; BAD, misleading about nesting
(def plusname name
     (if (isNaN (Number (last name)))
     (+ name 0)
     (+ (init name) (+ 1 (Number (last name))))))

; GOOD, reflects branching properly
(def plusname name
     (if (isNaN (Number (last name)))
         (+ name 0)
         (+ (init name) (+ 1 (Number (last name))))))

When nesting isn’t deep, try lining up each next indent with the second word on the preceding line (example above). Otherwise, stick with two spaces for each new level.

Why Use Jisp

Simple and Safe

Despite being more powerful, jisp is a lot simpler than JavaScript. Is has practically no syntax; there’s no semicolons, commas, or linebreaks to trip yourself over, no special rules for keywords and operators. Everything uses the same rules, making it hard to make an error.

It also absracts away legacy implementation details like var, break and the primitive for loop, eliminating entire classes of easy to make and hard to spot errors.

Powerful

At its heart, jisp is just JavaScript. But it’s also much more.

On the surface level, it builds some coding patterns right into the language and provides powerful higher-level conditionals and loops, making your programs terser. Its expressive functional syntax and implicit value resolution lets you focus on your ideas and have the language take care of data returns.

More importantly, it lets you define syntactic abstractions and automatically generate code, reprogram and extend the language, implement embedded domain-specific languages on top of JS, deduplicate code in ways impossible with plain JavaScript.

Why Jisp Over [insert dialect X]

There’s a bunch of Lisp-JavaScript dialects floating in the wild. So why jisp?

JavaScript-first

Most other Lisp-JavaScript implementations are attempts to port a [language X] to JavaScript. In best cases, they carry legacy design details that don’t make sense in the JavaScript environment or obfuscate the code (example: artificial distinction between arrays and code in most dialects). In worse cases, they clog the runtime with a reimplementation of another language on top of JavaScript.

Jisp is JS-native and axiomatic. Is has no needless concepts, no legacy syntax, and no runtime cost. Jisp focuses on the core ideas of code-as-data, S-expressions, macros, and brings them to JavaScript, introducing as few new concepts as possible. Everything else is left looking familiar and fits intuitively within the new syntax.

It also carefully abstracts away legacy JavaScript pitfalls, making the language safer without introducing alien concepts as a cost.

Jisp doesn’t target an [insert language X] programmer. It targets the JavaScript programmer.

Featureful

Jisp is full of features of immediate, practical use to a JavaScript developer. It’s not different for difference sake: it enables a whole new level of abstraction and makes full use of it, coming prepackaged with powerful tools. It takes practical features from other modern languages and the future of JavaScript and gives them to you now. Built-in macros and functions, shortcuts to common patterns, import and embedding for macros and global functions, spreading, destructuring, and more.

Axiomatic

Despite aiming for features, jisp takes the minimalistic, simplest possible approach to design. It wants you to type less and do more with less code. It doesn’t try to imitate [language X] — or JavaScript, for that matter. It aims to be succinct and get out of your way.

Acknowledgements and Notes

Jisp is massively inspired by CoffeeScript and uses bits of its source for CLI utils. Design inspiration from Arc and the Lisp family of languages, bits from other places. General inspiration from Arc-js.

Reach me out by instant messaging (preferably Skype) or email. See my contacts here. The email is also a Jabber ID.

Copyright (c) 2014 Mitranim, under the MIT License (source / docs).