Skip to content

Latest commit

 

History

History
626 lines (418 loc) · 14.8 KB

basics-reference.markdown

File metadata and controls

626 lines (418 loc) · 14.8 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.

Summary

Operators

Arithmetic ops: + - * / %
Bitwise: & | << >> >>> ~
Logic: && || !
Comparison: ===== != !== < > >= <=
Assignment: = += -= *= /= %= <<= >>= >>>= &= |= ^=
Increment / decrement: ++ -- ++_ --_ _++ _--

General

name description
array array literal
object object literal
regex regular expression literal
var variable declaration
. member expression
get computed member expression
switch switch statement
if conditional statement
?: ternary expression
while while loop
dowhile do-while loop
for for loop
forin for-in loop
break break statement
continue continue statement
label labeled statement
lambda function expression
function function declaration
return return statement
new new-expression
debugger debugger statement
throw throw statement
try try-catch statement

Structural

name description
seq comma sequence expression
block block statement

Macro-related

name description
macro macro directive
macroRequire loads a macro from .esl file
quote quotation operator
quasiquote quasiquote

These are only valid inside quasiquote:

name description
unquote unquote
unquote-splicing unquote-splicing

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;

Declaration and assignment

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

(var x)
(var 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

Function expressions

The lambda 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.

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

You can also give a name to a function expression as the optional first argument, if you so wish.

(var f (lambda tea () (return "T")))
var f = function tea() {
    return 'T';
};

Function declarations

These work much like function expressions above, but require a name.

(function tea () (return "T"))
function tea() {
    return 'T';
}

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.

(var 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 (var 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 (var 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.

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

If you want labeled statements, use label. You can break or continue to labels as you'd expect.

(label x
       (while (-- n)
              (while (-- n2) (break x))))
x:
    while (--n) {
        while (--n2) {
            break x;
        }
    }

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.

Loading existing macros

From an .esl or .js file that exports a function:

(macroRequire macroName "./path/to/file.esl")

From an .esl or .js file that exports an object which properties you want to load as macros:

(macroRequire "./path/to/file.esl")

From an npm module that exports a function:

(macro macroName (require "module-name"))

From an npm module that exports an object which properties you want to load as macros:

(macro (require "module-name"))

Defining your own 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.