The subset of JavaScript interpreted by Jispy is fondly called LittleJ (or LJ). It is a strict subset of JavaScript, strict in two senses:
- LJ is a syntactic and semantic subset of JS.
- LJ is far less permissive than JS.
Thus, every valid LittleJ program is also a valid JavaScript program, but every valid JavaScript program is not a valid LittleJ program.
Note: This document and documents linked herein are work in progress. However, this document is likely to retain most of it's current form.
LJ borrows heavily from Douglas Crockford's JS Conventions. In a few cases, it is stricter than JSLint.
Most programming languages have some notion of emptiness. It may be called void
, None
, null
, nil
etc. JavaScript calls it undefined
and also has null
. This can be very confusing.
JavaScript's overindulgence in undefined
masks otherwise easily detectable errors. Choosing to drop it makes the language far more robust and dependable. undefined
has excluded from LittleJ. It may not be used. This has the following implications:
In JS, the value of a declared but uninitialized variable is undefined
. Since there no undefined
in LJ, variables must have an initial value. Mere declaration is illegal.
// Thus,
var a, b, c, d, e, f, g; // is ILLEGAL (SyntaxError);
// But,
var a = null, b = null; // is legal
Long var
statements are not pretty. You can easily avoid them by using a container/namespace for your code.
var myApp = {};
myApp.PIE = 3.142;
myApp.square = function (x) { return x * x; };
// ... etc. ...
In JS, functions that don't have an explicit return statement return undefined
. As LJ lacks undefined
, all functions must explicitly return something.
// Thus,
sayHi = function (name) { print('Hi ' + name + '!'); }; // is ILLEGAL (TypeError)
// But,
sayHi = function (name) { print('Hi ' + name + '!'); return null;}; // is legal.
In JS, if you pass too few arguments to a function, the remaining arguments take undefined
. As there's no undefined
in LJ, you may not pass too few arguments to any function. For consistency, passing too many arguments has also been made illegal.
// Thus,
var add = function () { return sum(arguments); }; // is ILLEGAL
// Use this:
var add = function (args) { return sum(args); }; // (legal)
In JavaScript, any variable which wasn't defined with a var
statement is assumed to belong to the global scope. Such variables are called implied globals and are very bad. They make your code inefficient and vulnerable.
To remedy this problem, LittleJ requires all variables to be initialized before use, via var
statements. Further, once initialized, they may not be reinitialized (in the same scope.)
// Thus,
var a = 10; b = 100; // is ILLEGAL. (Assuming b was not defined in an enclosing scope.)
// Similarly,
var a = 10; var a = 100; // is also ILLEGAL.
// But,
var a = 10, b = 100; a = 100; // is perfectly legal.
JavaScript does NOT have block scope. It has functional scope. Initializing a variable just before its point for first use is very BAD advise in JavaScript.
In particular,
".. defining variables in blocks can confuse programmers who are experienced with other C family languages. ... " *~ Douglas Crockford*
To clarify scopes, there may be at most one var
statement per scope. If a var
statement is used in a scope, the first token within that scope must be var
. This is also applicable to the global scope.
// Thus,
var a = 1, b = 2; var c = 3; // is ILLEGAL
// But,
var a = 1, b = 2, c= 3; // is legal.
Because of this rule, even if a programmer is unaware of functional scoping, he won't land into any scope-related misunderstanding.
Functions are one of the very best parts of JavaScript. LittleJ is proud to include them. The meaning of the phrase "Functions are first class citizens" is:
- A function may return a function.
- A function may be passed (as an argument) to a function.
- A function may close over other functions. (Closures are fully supported.)
As with most things in JavaScript, there is a bad part associated with function declarations, namely function hoisting. It allows us to use functions before we declare them. Moreover, it makes your code confusing.
Luckily, function values (created via function expressions) are not hoisted. LittleJ supports function expressions only. Funtion declarations have been dropped.
// Thus,
function add(a, b) { return a + b; } // is ILLEGAL (SyntaxError).
// But,
var add = function (a, b) { return a + b; } // is legal
When a function is to be invoked immediately, wrap the entire invocation in a pair of parenthesis. This not only makes your intentions clear, but (in some cases) is required by JavaScript.
//That is, use,
(function (x) { return x * x; }(2));
//instead of,
function (x) { return x * x; }(2);
Note: Currently, only anonymous functions are supported. This is not likely to change in the near future. (Anonymous functions may be recursive.)
Getting rid of undefined
exposed a huge class of errors that may go unchecked in JS. Getting rid of NaN
and Infinity
as well makes the programming extremely reliable, robust and clear.
Like undefied
; NaN
and Infinity
mask too many errors that may otherwise be easily detected. We are better off without them.
// That is,
a = 'James Bond' / 700; // is ILLEGAL (TypeError). It is not NaN.
// And
a = 99 / 0; // is also ILLEGAL (ZeroDivisionError). It is not Infinity.
Unfortunately, there is no ZeroDivisionError
in JavaScript. Keeping that in mind, consider the following snippet of code:
var a = null;
try {
a = 1/0;
print('This is JS');
} catch (e) {
print('This is LJ');
}
In JavaScript, the output would be This is JS
. In LittleJ, on account of ZeroDivisionError
one may expect the output to be This is LJ
, but wait! The moment any program has a different meaning in JavaScript than in LittleJ, LJ ceases to be semantic subset of JS. This contradicts our premise!
Thus, unfortunately, try
-catch
blocks are not included in LittleJ. If you can come up with a way to include them meaningfully, please let me know. I shall be forever grateful.
The above problem is not specific to ZeroDivisionError
, it applies to all errors thrown by LittleJ but not by JavaScript.
In JavaScript, there are four function invocation patterns. The value of this
is determined by the invocation pattern used. Apart from making JavaScript look a lot like Java, this
rarely does anything useful.
Unfortunately, JavaScript's constructors do a very great job at diverting our attention from its truly prototypal nature. Why do we need to use constructors (or equally classes) when objects are object factories! We shouldn't.
As there's no this
, there cannot be any constructors in LittleJ. Instead, we use builders:
// ... preceeding code ...
buildPoint = function (x, y) {
var self = {},
square = function (n) { return n * n; };
self.getX = function () { return x; };
self.getY = function () { return y; };
self.dist = function (pt) {
return math.sqrt(square(x - pt.x) + square(y - pt.y));
};
return self;
};
origin = buildPoint(0, 0);
// ... more code ...
For comparison, here's the JS equivalent of above code:
// ... (possibly) preceeding code ...
Point = function (x, y) {
var square = function (n) { return n * n; };
this.getX = function () { return x; };
this.getY = function () { return y; };
this.dist = function (pt) {
return Math.sqrt(square(x - pt.x) + square(y - pt.y));
};
return this; // (optional)
};
origin = new Point(0, 0);
// ... more code ...
Before delving into the difference between the above two, please take a minute to observe the similarities. The differences are discussed in the following sections.
In JS , if you forget the new
operator, this
will be bound to the global object (window
in browsers.) Thus, getX()
, getY()
and dist()
will be added in the global namespace! This is very bad!
Excluding this
not only prevents you from making the new
blunder, but is equally (or arguably more) expressive.
In JS, names of constructors conventionally begin with a capital letters. Being mindful of this convention, in LittleJ, identifier names may not being with an upper case letter.
There are two more limitations, namely:
- Identifier names may not include the
$
key. - Identifier names may not include dangling (i.e. leading or trailing) underscores.
this
comes with its share of goodies, namely the prototype chain. Prototypal inheritance is perhaps the best model for code reuse. However, JavaScript's implementation of prototypal inheritance is confusing (and perhaps irritating.)
As there are no constructors in LittleJ, JavaScript-like prototype chains are not supported. However, the stdlib.l.js
library (currently under development) should largely fill this void. It contain an object.create()
method analogous to JavaScript's Object.create()
.
The in
keyword in JavaScript (notoriously) looks for properties up the prototype-chain. Including it in LittleJ would lead to semantic inequality (with JavaScript). It has hence been excluded.
As there's no prototype chain, objects do not have any magically acquired properties. An object has exactly those properties which were explicitly put in it. The same goes with arrays and all other datatypes.
Unfortunately, this means that arrays and strings don't even have useful properties like length
. Here's where inbuilt functions come into play.
There are 9 inbuilt functions, complimented by the inbuilt math
object. Each inbuilt has an equivalent in JavaScript, which can be defined in a few lines of code.
JavaScript's typeof
operator is not particularly helpful. In fact, it is confusing. In LittleJ, it has been replaced by a more meaningful type()
function.
type(x)
may be one of:
"boolean"
,"number"
,"string"
,"array"
,"object"
,"function"
,"null"
.
JS Equivalent:
var type = function (x) {
if (x === null) { return 'null'; }
if (Object.prototype.toString.call(x) === '[object Array]') { return 'array'; }
return typeof x;
};
del()
replaces JavaScript's delete
operator. It accepts two arguments, an object and key or an array and index. This makes it clear that del()
cannot be used to delete variables.
JS Equivalent:
var del = function (x, y) {
if (type(x) === 'array') { // above defined `type()` used here
x.splice(y, 1);
return true;
} else { return delete x[y]; }
}
As opposed to delete
, del()
does not leave any undefined
holes when deleting from arrays.
The length of arrays, strings and objects is available via the len()
function. The len()
of an object is the number of keys it has.
JS Equivalent:
var len = function (x) {
if (type(x) === 'object') { return Object.keys(x).length; }
return x.length;
};
type()
defined in section 6.1 was used above. If you don't like this treatment, then use:
var len = function (x) {
if (typeof x === 'string') { return x.length; }
return Object.keys(x).length;
};
keys()
in LittleJ returns the all the keys in an object.
JS Equivalent:
var keys = Object.keys;
In JS, trying to retrieve a non-existent key from an object results in undefined
. There is no undefined
in LJ; instead, a KeyError
is thrown. The stdlib.l.js
library has a function object.hasOwnProperty()
which should be used in case of uncertainty.
str(x)
is the string representation of x
. If an array or object is passed to str
, its JSON-like representation is returned.
JS Equivalent:
var str = function (x) {
if (typeof x === 'object') { return JSON.stringify(x); }
return String(x);
};
This function appends an element to the end of an array.
JS Equivalent:
var append = function (arr, elt) { return arr.push(elt); };
Provides a way to throw an AssertionError
if the current state of the program is unexpected or detrimental. It takes two arguments, the expression to be asserted and the message to be displayed on failure.
JS Equivalent:
var assert = function (expr, msg) {
if (expr) { return null; }
throw new Error('AssertionError: ' + msg);
};
Returns the integer ordinal of an one-character string.
JS Equivalent:
var ord = function (c) { return c.charCodeAt(0); };
Returns a string of one character with supplied ordinal. Input should be in the range 0 to 256, both included.
JS Equivalent:
var chr = function (i) { return String.fromCharCode(i); };
The same as Math
in JavaScript.
JS Equivalent:
var math = Math;
This is a non-standard output function. Jispy provides it by default, but other implementations of LittleJ may not.
7.12 setupLJ.js
Before running a LittleJ program in a full JavaScript environment, the environment should be setup for LittleJ. That is, LittleJ's inbuilts discussed above should be defined. setupLJ.js does exactly that.
Thesedays, we realize that a high level programming language such as JavaScript doesn't need bitwise operators. They are not included in LittleJ.
Javascript's ==
and !=
operators are nothing but a recipe for disaster. They, likewise, are not included in LittleJ. Please stick to ===
and !==
operators.
The increment and decrement operators are not really required. Use shorthand assignment +=
and -=
instead. This practice results in more reliable and more readable code.
Try the following code in your browser's console. It might shock you.
console.log(1 === 1 === 1); // --> false; (true === 1) is false
console.log(1 > 1 < 1); // --> true; (false < 1) is true
console.log(0 >= 0 >= 1); // --> true; (true >= 1) is true
The above example aptly demonstrates that JavaScript's relational operators should not be chained. LittleJ doesn't allow you to.
An exception is made when if
immediately follows else
.
Further, if
, while
and for
statements must be supplied control conditions.
// Thus,
while () { "cheeky expression"; } // is ILLEGAL, (no condition)
while (true) { } // is also illegal (empty block).
// But,
while (true) { "cheeky expression"; } // is legal.
The following JS keywords are meaningful in LJ:
var if else while for break function return true false null
All other JS keywords along with the words undefined
, NaN
and Infinity
are meaningless in LJ and may not be used.
The library stdlib.l.js is written purely in LittleJ. It should serve as a meaningful example. It contains a collection of string, array and object manipulators. The library is currently under active development and may change from the time of writing. (Its name is likely to change to utils.l.js or underbar.l.js)