Skip to content

Latest commit

 

History

History
495 lines (324 loc) · 11.2 KB

basics-reference.markdown

File metadata and controls

495 lines (324 loc) · 11.2 KB

Eslisp basics tutorial

This page explains how to read eslisp code and what all the built-in macros do. If you're particularly interested in how macros work, see the macro tutorial.

The compiler

The eslisp package comes with a compiler program eslc, which reads eslisp code on stdin and emits corresponding JavaScript on stdout.

The only particularly interesting flag you can give it is --transform/-t, which specifies a transform macro for changing something about the entire program. Examples include supporting dash-separated variables (eslisp-camelify) or shorthand property access (eslisp-propertify). These are just for sugar; you can use them if you want.

Syntax

Eslisp code consists of S-expressions. These are lists that may contain atoms or other lists.

Examples:

  • In (+ 1 2), the parentheses () represent a list, containing three atoms +, 1 and 2.

  • (a (b c)) is a list containing an atom a and another list containing b and c.

There are 3 kinds of atom in eslisp:

Atoms with double quotes " at both ends are read as strings. (e.g. "hi" or "39".) All "opened" double quotes must be closed somewhere.

Atoms that consist of number digits (09), optionally with an embedded decimal dot (.) are read as numbers.

All other atoms are read as identifiers—names for something.

You can also add comments, which run from the character ; to the end of that line.

Whitespace is ignored outside of strings, so these 3 programs are equivalent:

(these mean (the same) thing)
(these
mean (the
same) thing)
(these mean (the
             same) thing)

This means you can indent your code as you wish. There are conventions that other languages using S-expression syntax use, which may make it easier for others to read your code. This tutorial will stick to those conventions.

That's all you need to know about the syntax to get started. (There is a little extra syntax that makes macros easier to write, but we'll talk about those later.)

Compilation

When you hand your code to the eslisp compiler, it reads the lists and turns them into JavaScript code with the following rules:

  • Strings become JavaScript strings.

    "hi"
    
    'hi';
    
  • Numbers become JavaScript numbers.

    42.222
    
    42.222;
    
  • Identifiers become JavaScript indentifiers.

    helloThere
    
    helloThere;
    
  • Lists where the first element is an identifier that matches a macro becomes the output of that macro when called with the rest of the elements.

    Here + is a built-in macro that compiles its arguments and outputs a JavaScript addition expression:

    (+ 1 2)
    
    1 + 2;
    
  • Any other list becomes a JavaScript function call

    (split word ",")
    
    split(word, ',');
    

Nested lists work the same way, unless the macro that they are a parameter of chooses otherwise.

That's all.

Built-in macros

Macros are functions that only exist at compile-time. A minimal set needed to generate arbitrary JavaScript are built in to eslisp.

Operators

Arithmetic

These take 2 or more arguments (except -, which can also take 1), and compile to what you'd expect.

(+ 1 2 3)
(- a b)
(- a)
(/ 3 4)
(* 3 (% 10 6))
1 + (2 + 3);
a - b;
-a;
3 / 4;
3 * (10 % 6);

Same goes for bitwise arithmetic & | << >> >>> and ~, logic operators &&, || and ! and pretty much everything else in JavaScript.

Increment and decrement

++ and -- as in JavaScript. Those compile to prefix (++x). If you want the postfix operators (x++), use _++/_--. (++_/--_ also do prefix.)

Delete and instanceof

The delete and instanceof macros correspond to the JS operators of the same names.

(instanceof a B)
(delete x)
a instanceof B;
delete x;

Variable declaration and assignment

Variable declaration in eslisp uses the = macro, and assignment is :=.

(= x)
(= y 1)
(:= y 2)
var x;
var y = 1;
y = 2;

The other assignment operators are the same as in JS.

(-= a 5)
(&= x 6)
a -= 5;
x &= 6;

Arrays and objects

Array literals are created with the array macro. The parameters become elements.

(array)
(array a 1)
[];
[
    a,
    1
];

Object literals are created with the object macro which expects its parameters to be alternating keys and values.

(object)
(object a 1)
(object "a" 1 "b" 2)
({});
({ a: 1 });
({
    'a': 1,
    'b': 2
});

Property access uses the . macro.

(. a 1)
(. a b (. c d))
(. a 1 "b" c)
a[1];
a.b[c.d];
a[1]['b'].c;

If you wish you could just write those as a.b.c in eslisp code, use the eslisp-propertify user-macro.

For computed property access, use the get macro.

(get a b)
(get a b c 1)
(:= (get a b) 5)
a[b];
a[b][c][1];
a[b] = 5;

For new-expressions, use the new macro.

(new a)
(new a 1 2 3)
new a();
new a(1, 2, 3);

Conditionals

The if macro outputs an if-statement, using the first argument as the condition, the second as the consequent and the (optional) third as the alternate.

(if a b c)
if (a)
    b;
else
    c;

To get multiple statements in the consequent or alternate, wrap them in the block macro.

(if a
    (block (+= b 5)
           (f b))
    (f b))
if (a) {
    b += 5;
    f(b);
} else
    f(b);

Some macros treat their arguments specially instead of just straight-up compiling them.

For example, the switch macro (which creates switch statements) takes the expression to switch on as the first argument, but all further arguments are assumed to be lists where the first element is the case clause and the rest are the resulting statements. Observe also that the identifier default implies the default-case clause.

(switch x
    (1 ((. console log) "it is 1")
       (break))
    (default ((. console log) "it is not 1")))
switch (x) {
case 1:
    console.log('it is 1');
    break;
default:
    console.log('it is not 1');
}

Functions

The function macro creates function expressions. Its first argument becomes the argument list, and the rest become statements in its body. The return macro compiles to a return-statement.

(= f (function (a b)
               (return (* 5 a b))))
var f = function (a, b) {
    return 5 * (a * b);
};

Loops

While-loops (with the while macro) take the first argument to be the loop conditional and the rest to be statements in the block.

(= n 10)
(while (-- n)
 (hello n)
 (hello (- n 1)))
var n = 10;
while (--n) {
    hello(n);
    hello(n - 1);
}

Do-while-loops similarly: the macro for them is called dowhile.

For-loops (with for) take their first three arguments to be the initialiser, condition and update expressions, and the rest to the loop body.

(for (= x 0) (< x 10) (++ x)
 (hello n))
for (var x = 0; x < 10; ++x) {
    hello(n);
}

For-in-loops (with forin) take the first to be the left part of the loop header, the second to be the right, and the rest to be body statements.

(forin (= x) xs
       ((. console.log) (get xs x)))
for (var x in xs) {
    console.log(xs[x]);
}

You can use an explicit block statements (with the block macro) wherever implicit ones are allowed, if you want to.

(= n 10)
(while (-- n)
 (block (hello n)
        (hello (- n 1))))
var n = 10;
while (--n) {
    hello(n);
    hello(n - 1);
}

Exceptions

The throw macro compiles to a throw-statement.

(throw (new Error))
throw new Error();

Try-catches are built with try. Its arguments are treated as body statements, unless they are a list which first element is an identifier catch or finally, in which case they are treated as the catch- or finally-clause.

(try (a)
     (b)
     (catch err
            (logError err)
            (f a b))
     (finally ((. console log) "done")))
try {
    a();
    b();
} catch (err) {
    logError(err);
    f(a, b);
} finally {
    console.log('done');
}

Either the catch- or finally- or both clauses need to be present, but they can appear at any position. At the end is probably most readable.

User-defined macros

If you can think of any better way to write any of the above, or wish you could write something in a way that you can't in core eslisp, check out how macros work to learn how to introduce your own.

Even if you don't care about writing your own language features, you might like to look into what user macros already exist, and if some of them might be useful to you.