![]() |
| We all want Mhorr Gazelle. |
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.
Getting Started
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). (I recommend byte-compiling all the parts of Gazelle - there is some pretty heavy macro magic involved.)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.Quick Syntax Notes
Gazelle is like Javascript but provides some thin wrappers on Javascript ideas.Dotted symbols are supported, so you can write.
(console.log "Hello World")
You can also write (.. console (log "Hello World"))
The former expands into the latter.Arrays are constructed using the following syntax:
[: a b c d]
The ":" is required, because[an-array 3]
represents the JavascriptanArray[3]
Objects are denoted via:(var object ({} x 10 y 11})
Object indexing is either
object.x
or
[object "x"]
For loops are written in the expected way, translated to s-expressions:
(for (i :in array-thing)
(do-something [array-thing i]))
They do not return a value. if expands to the ternary conditional. Eg:(if cond e1 e2)
is(cond ? e1 : e2)
One can write a "flat" if using the primitive if, eg;(_if cond (true-branch0 true-branch ...)
(false-branch0 false-branch))
This does not produce a value. The code in prim is quite readable,
and proper, for the most part, provides non-underscored versions of
many primitive operations.Using Modules
Gazelle's module system is based onrequire.js, with extensions and
provisions for the fact that Gazelle has both run-time and
compile-time entities which it needs to scope to modules. To use the
system, you must set up your project directory as in the require.js
documentation. Gazelle also
needs to know about the location of your project directory. It can
find out about this in two ways. The first is to typeM-x gz:set-project-directory
And then enter the scripts directory for your project. The
alternative is to do nothing, and the first time Gazelle needs to
use the project directory, it will ask for one. Afterward, if need to
set the project directory, use the above command.Your page should look like the
require.js example page, eg:<!DOCTYPE html>
<html>
<head>
<title>My Sample Project</title>
<!-- data-main attribute tells require.js to load
scripts/main.js after require.js loads. -->
<script data-main="scripts/main" src="scripts/require.js"></script>
</head>
<body>
<h1>My Sample Project</h1>
</body>
</html>
And your entry point should be in scripts/main.gazelle. And it
should look something like this:(require
(("hooves/operator-functions" :all)
("hooves/lisp-idioms" (:with-prefix idioms- :all))
("jquery/jquery" (:as $)))
(_+ 1 1)
(var x 10)
(incr x)
(console.log (_+ "X minus one is " (- x 1)))
(console.log (+ "7 < 10" (< 7 10)))
(console.log (+ "7 > 10" (> 7 10)))
(console.log (+ "using idioms- " (idioms-apply + [: 1 2])))
(.. ($ "body") (append "Hello World."))
(+ 1 2))
Here we have just a simple example to demonstrate the module syntax.
require is a special form which compiles to a call to the
require.js function call of the same name (how to do this is
explained in the manual). The syntax is
(require <list-of-module/import-specifiers> body0 body ...)
Each module import specifier is of the form (<module designator string> import-directives)
An import directive is one of (:as import-mapping)
:all
(:with-prefix prefix import-directive)
An import mapping is one ofa symbol
a list with one symbol
a list with two symbols
The meaning of a single symbol is to take the external symbol in the
imported module and map it onto a local symbol. The list with a
single symbol has the same meaning. When two symbols are present, the
first designates the name of an entity in the module and the second
designates the local name. An
:all import directive imports all external entities from the
module to identical names in the current module.:with-prefix is a kind of import directive modifier. It expands the
inner import directive and then prefixes all the local names with the
indicated prefix. So in our example, the lisp-idioms module exports
symbols funcall and apply. The ("hooves/lisp-idios"
(:with-prefix idioms- :all)) creates module local bindings
idioms-funcall and idioms-apply.Local bindings to values in an imported module are not actual variables, but symbol macros, so you can
set! them at get the
appropriate behavior. That is, if a value is referenced inside a
module, and it is imported into another module, expressions in both
modules refer to the same Javascript object. Once you have your
main.gazelle and your modules, simple perform a
gz:transcode-file on main.gazelle. This will transcode all of the
files via the module system. Module transcoding is cached against the
md5 hash of the module file, so if you change the module, the next
recompile of main.gazelle with force a recompile of the module too,
but unnecessary module recompilation is avoided. You can reset the
cache by M-x proper:reset-module-cache
In this way the module system doubles as the build system.
Here is an example of a module file (located at
hooves/lisp-idioms.gazelle):(module
()
;; lisp-idioms
;; this module defines funcall and apply
(define+ (apply f an-array)
(.. f (apply undefined an-array)))
(define+ (funcall f (tail args))
(apply f args)))
Inside a module, define defines local module values or functions,
and define-macro defines local macros. define+ defines an
external function or value and define-macro+ defines an external
macro. define is like define in Scheme, in the sense that it can define
either functions or values, except that any place an argument symbol
can be used, you can specify a shadchen-like pattern. To collect any
additional arguments into an array, use the pattern (tail tl) in the
argument list. define produces functions which automatically return the value of the last expression in their body, if that makes sense. You can define plain Javascript functions by using the primitive form _function. How it Works
Gazelle is made up of several parts. Theprim module (for
primitive) is a transcoder that takes an s-expression representation
of all of Javascript's primitives and transcodes them to Javascript
itself. For instance,(_function (arg-one arg-two) (_return (_+ arg-one arg-two)))
Is a valid prim expression which transcodes to:(function(argOne, argTwo) { return (argOne+argTwo); })
Generally speaking, prim is simple to understand. Each javascript
primitive is assigned a symbol beginning with an underscore, and that
is transcoded in a straightforward manner to Javascript. Prim does not support your familiar Lisp idioms, though they are in
Gazelle itself. Prim has just one interface, the emacs lisp
function prim:transcode, which inserts the transcoded representation
of its input argument into the current buffer. Generally, you won't
need to worry about this.The module
proper is where most of the heavy lifting of Gazelle
takes place. Despite this fact, Proper is fairly simple, amounting
to slightly more than the construction of a macro system on top of
prim. That means that almost all special forms in proper and
therefore in Gazelle itself are implemented as regular macros.
Proper implements a lexically scoped, static macro system, which
means that it is possible, but not typical to, to shadow special forms
in limited contexts. There is also a symbol macro system, which is
partially exposed and used "under the hood" to implement advanced
features.In the course of typical compilation,
proper macros can expand to
additional macro definitions and the behavior is consistent. Code is
passed through multiple compilation passes until it reduces entirely
to prim operations.Proper has several entry points because it needs to know about the
one advanced feature in Gazelle which is not in Javascript itself,
modules, of which more later.Gazelle is available at my github. Along with a lot of other cool/ill conceived stuff

4 comments:
lispyscript already exists....
It did not seem like Lispyscript's macro system was powerful enough for me, plus, writing my own was fun.
I'm already using `json.el' to wrap JSON API calls (at work) in a sane Lisp syntax. This looks like a *really* appealing alternative to writing raw Javascript; the fact that it's in Emacs Lisp makes it so easy to start with. Thanks for writing it, and for sharing it with the rest of us!
Gazelle is still under active development, so be sure to merge often if you want the latest features and bugfixes.
Just this morning I added support for using modules in node.js and for requiring pure javascript modules in the require/module syntax.
Post a Comment