Gazelle is a rewrite of an aborted project, jsel, which aims to be a sensible Lisp for Javascript. By "sensible" I mean it adheres to the following ideas:
- Be yourself: Gazelle doesn't try to be or imitate another Lisp. It is based on an s-expression representation of javascript itself and the base idioms of the language are the base idioms of javascript. Any other idioms are meant to be added via the macro system.
- Stay Organized: Despite the above, Gazelle has a few built in extensions to the basic Javascript paradigm. One of them is a module system for both static and dynamic (macros and run-time values) objects, based on require.js
- I never metaprogram I didn't like. That is, be a Lisp: Gazelle provides a powerful macroexpansion language (Emacs Lisp + shadchen) which transforms a s-expression representation of javascript before the s-expression to javascript compiler is invoked. The idea behind Lisps is extension of the base language, so Gazelle exposes javascript for extension.
Unsensibly, Gazelle is written in Emacs Lisp. I'll port it to Common Lisp eventually.
You'll need GNU Emacs, which hosts the entire project.
You'll also need this repository and shadchen-el.
you@home:~$ cd emacs-code # or wherever you put your emacs stuff
you@home:~/emacs-code$ git clone https://github.com/VincentToups/shadchen-el.git
you@home:~/emacs-code$ git clone https://github.com/VincentToups/gazelle.git
Then, in your emacs configuration, either .emacs.d/init.el
or
.emacs
add lines to the effect of:
(push "~/emacs-code/shadchen-el/" load-path)
(push "~/emacs-code/gazelle/" load-path)
If you want to use Gazelle you then must, at some point, (require 'gazelle)
.
Gazelle can be used in two ways. The first is the simplest, the
function gz:transcode-file
takes the contents of a .gazelle
file
and outputs a .js
file with the same filename in the same directory.
(Optionally, an alternative output file name can be specified.) One
could develop their Javascript project in separate files this way and
never used Gazelle's module system. However, the module system
provides powerful features for code organization.
*** Primitive Syntax
Gazelle is based on a primitive s-expression representation of Javascript. Each Javascript primitive is supported in Gazelle by a symbol with a similar name preceeded by an underscore. This allows you to write Gazelle code which translates almost directly into the equivalent Javascript. For instance:
(_if (some-condition)
((do-something)
(do-something-else))
_else
((do-another-thing)
(do-yet-another-thing)))
Will transform directly into:
if someCondition() {
doSomething();
doSomethingElse();
} else {
doAnotherThing();
doYetAnotherThing();
}
Usually Gazelle exposes a non-underscored symbol which has the same
behavior as the underscored one, but not in the case of if
. if
is
usually a value-producing expression in Lisp, so
(if expr true-expr false-expr)
produces:
(expr ? true-expr : false-expr)
Symbols with dots in them are allowed, so
`x.some-thing.z`
becomes
`x.someThing.z`
However, to support full Javascript chaining syntax, the user must us
the _.
expression.
eg:
(_. a (a b c) d (e f) g)
Is
a.a(b,c).d.e(f).g
See prim.md for documentation of the primitive layer.
*** Gazelle Syntax
Gazelle is a much richer language than just the basic Javascript as-s-expressions syntax above implies.
Here is a brief highlight of Gazelle's language features:
(define (f a b c) (_+ a b c))
Defines a function which takes three arguments. define
is a very
powerful special form because it allows one to define functions which
perform nestable pattern matching on their arguments.
(define (f [: a b c])
(_+ a b c))
Defines a function which accepts only a three element array as input,
binds each element to a
b
and c
and adds those values.
(define (g { q a
r b
s c })
(+ a b c))
The function g
defined above accepts an object with properties q
,
r
, and s
and binds those values to a
, b
, and c
. Patterns
are nestable, so the function h
(define (h [: a { q i r j s k } b])
(_+ a i j k b))
Acccepts an array of three elements, the second of which is an object,
binds a
and b
to the first and last elements of the array and i
,
j
, and k
to the entries at q
r
and s
in the middle object.
Gazelle's pattern matcher is powerful and extensible, capable of performing and abstracting arbitrary combinations of destructuring, type assertion, and computation.
One can also define values with define
, not just functions:
(define x 10)
Is more or less equivalent to var x 10
.
(var-match <pattern> <value>)
Allows you to introduce variable definitions with pattern matching.
Gazelle function definitions can also use pattern matching to dispatch to different computations and recur upon themselves.
We can write a product
function like so, for instance:
(define (product
[(([:] acc)
acc)
(([: hd (tail tl)] acc)
(recur tl (* acc hd)))
((seq)
(recur seq 1))]))
Where recur
here is tail-optimized recursion, and so does not grow
the stack. Pretty neat!
The pattern matching engine is extensible via
(defpattern positive-number (sub-pattern)
`(number (p (_function (x) (_return (_> x 0))) ,sub-pattern)))
Which allows us to write function which takes only positive integers as:
(define (f (positive-number x)) (Math.sqrt x))
In addition to these fancy pants features, Gazelle provides a match
expression which lets you apply pattern matching to any value,
non-underscored versions of all the prim fundamental Javascript
operators (except for _if
), cond
statements, progn
, let
and a
variety of other niceties.
*** Macros
Gazelle provides translation-time meta-programming features in the form of macros.
A macro is introduced via
(define-macro (increment x) `(set! ,x (_+ ,x 1)))
And allows us to write
(var x 0)
(increment x)
(console.log x)
And get Javascript like:
var x;
x = x + 1;
console.log(x);
See proper.md for information on the language-level features of Gazelle.
Much effort has gone into making Gazelle a more modular language than Javascript. To that end, Gazelle is integrated with the require.js Javascript module system, which allows you to use both Javascript modules written for the browser and for Node.js and Gazelle modules. In the latter case, modules provide a mechanism to organize both Javascript values and static objects like macros and custom patterns for Gazelle's pattern matcher.
*** In a Browser
Follow the set up instructions at the require.js page. This
involves creating a scripts
directory and placing a project entry
point. Your html page will have a line like:
<script data-main="scripts/main" src="scripts/require.js"></script>
Now create a main.gazelle
in scripts
, open it with emacs, and
type:
(require ()
(console.log "Hello World.")
Press C-x C-l, tell emacs that main.gazelle is your project's entry
point, and that the scripts directory is scripts
and Gazelle will
produce a main.js
. You should now be able to load your code in your
browser!
*** Node Setup
Using Gazelle with node only that you install require.js
via
npm install require
And that you use the form node-require
instead of require
in your
project entry point. Modules are portable across node
and web
browsers.
A module should be placed somewhere in your scripts directory, say:
/scripts/examples/example.gazelle
And look something like this:
(module (("hooves/hooves" :all))
(define+ (say-hi) (console.log "Hello World")))
This defines a module which exports a single function called say-hi
.
We can use it in our main
like so:
(require (("examples/example" :all))
(say-hi))
Pressing C-x C-l again will cause Gazelle to recompile the entire project. Gazelle is smart enough to only rebuild those parts of your project which have changed or depend on parts which have changed, which means in routine development, compilation is quite fast.
Gazelle produces pure Javascript code which is no longer dependent on Gazelle and can be further minified or obfuscated using the tools of your choice.
Gazelle is becoming a rich platform for developing applications that target both server and client side Javascript ecosystems with most of the conveniences of s-expression based editing, macros and pattern matching in a module oriented programming platform.
I am already using it for non-trivial work, but would love it if other's contributed to the project's forward development.