Setting Up
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). This will also define a gazelle mode with some handy
keybindings and syntax highlighting. I recommend byte-compiling the whole of Gazelle and Shadchen.
scratch.el from the Gazelle repository contains code for doing that
for Gazelle. Shadchen is just one file, shadchen.el, and is easier
to byte compile.Our Goal
My spouse is learning sign language. She had an assignment for class that required her to spell three words in front of the class and she thought it might be fun to try and pick three words that covered the entire alphabet, if this is even possible.We are going to write a Gazelle project that displays three text input areas and updates, in real time, a display of the all the letters of the alphabet that are not used. That way you can experiment with different combinations of words to try and get as many letters as possible.
Getting Started
First create a new directory for your project, eg:# mkdir -p src/gazelle/three-words
# cd src/gazelle/three-words
Then, inside that directory, create a scripts directory. We want to
create symbolic links to the Gazelle standard library, called hooves
and to the Gazelle stub library that allows you to use jquery.
When you deploy the project, you'll use cp -rL to copy the contents
of those directories instead of the symbolic links. # mkdir scripts && cd scripts
# ln -s $GAZELLE_PATH/scripts/hooves hooves
# ln -s $GAZELLE_PATH/scripts/jquery jquery
You'll also need require.js, which you can link to in the Gazelle
repository:# ln -s $GAZELLE_PATH/scripts/require.js require.js
Finally, create a main.gazelle file. This will be our entry point. # touch main.gazelle
We'll come back to main.gazelle in a second, but first we have to
make our page. Go up one directory, to the project directory, and
create an index.html file and fill it in with this:<!DOCTYPE html>
<html>
<head>
<title>Cover the Alphabet</title>
<link href="css/toast.css" type="text/css" rel="stylesheet">
<link href="css/styles.css" type="text/css" rel="stylesheet">
<!-- data-main attribute tells require.js to load
scripts/main.js after require.js loads. -->
<script data-main="scripts/main.js" src="scripts/require.js"></script>
</head>
<body>
<div class="wrap">
<div class="grids">
<div class="grid-12">
<h1 class="title">Find Three Words Covering the Alphabet</h1>
</div>
<div class="grid-12">
<div class="label">The Leftover Letters: </div>
<div id="letters" class="letter-list">abcdefghijklmnopqrstuvwxyz</div>
</div>
<div class="grid-4">Word 1:<input class="word" id="word-1"></input></div>
<div class="grid-4">Word 2:<input class="word" id="word-2"></input></div>
<div class="grid-4">Word 3:<input class="word" id="word-3"></input></div>
</div>
</div>
</html>
Absolutely critical here is: <script data-main="scripts/main.js"
src="scripts/require.js"></script>
This is the require.js entry point, which tells the browser that
main.js, which will be generated from main.gazelle, is the entry
point for the Javascript to run on this page.I'm using the Toast grid framework and some custom css, which you can download in the repository here, but this project will run without that stuff, it just won't look nice. The operative elements here are the one with the
id "letters", which will contain
the leftover letters of the alphabet, and the elements with class
"word". Ok! Our HTML page is set up, now open up
main.gazelle in your
browser. main.gazelle
Gazelle does not provide default definitions of functions corresponding to operators, like+, -, <, etc. So the first
thing we need to do is require the modules from hooves that define
operator functions. N.B.:
require in require.js is a function, but it is a macro in
Gazelle. (require
(("hooves/operator-functions" :all)
("hooves/hooves" :all))
(console.log (+ "Hello " "World!")))
require, in Gazelle, takes a list of module require forms as its
first arguments. The rest of the form constitutes the body to be
executed in the context of those requirements. Here we require two
modules, hooves/operator-functions and hooves/hooves, which
defines utility functions and macros. :all after each indicates
that we want to use all of the exported objects from those modules
under the names that those modules use. We could use only a subset by
specifying an (:as (local-name module-name) ...) form instead. We can now compile this project and test the results. Invoke the transcoder by invoking
gz:transcode-this-file, bound to C-c C-k in
gazelle-mode. Gazelle uses module dependencies to guide the build
process, so when using the module system, one need only compile
main.gazelle, any modules that are required and that have changed
in some way will be recompiled. (N.B.: Gazelle will ask you, the first time you compile, to enter your project directory. It should be the
scripts directory, which is the
default response. You can change it later by invoking
gz:set-project-directory, if you want to switch to a new project.)Now direct your browser to
index.html and open up your debugger.
You should see Hello World!, which means that our module system is
working, because + is a function defined in operator-functions.
Without that requirement, this code would generate an error.Solving our Problem
Ok, let's get to work. The basic operation here is to take one string and remove all the letters from it that occur in another string. This is aset-diff function. Easy to write, but where do we put it?We could just write that code in our
main.gazelle, but let's use the
module system, why not. Use emacs to open a file called
scripts/three-words/three-words.gazelle. Because Emacs is awesome,
it will prompt you to create the directory by entering M-x
create-directory ENTER ENTER, which you should do. Then add the
following to the file:(module
(("hooves/operator-functions" :all)
("hooves/hooves" :all)
("jquery/jquery" :all))
(define (set-diff set1 set2)
(var out [:])
(for* ((index element) :in set1)
(var i (set2.index-of element))
(if (=== i -1)
(out.push element)))
out))
This code is a straightforward module. The first form is the same as
the first form in a require, it indicates that in this module we
depend on and use the operator functions and the hooves module, as
well as jquery. The
define form introduces a private function which calculates a
set difference. Inside the module we refer to it with set-diff, but
no one outside the module can access it. The function returns the
elements in set1 that are not in set2, as an array. We can now write the real work horse, a function which reads the strings from our word inputs, concatenates them, and then removes all those letters from the complete alphabet, before setting the
text of
the correct HTML element, with jquery, to the result.Add this to the module body:
(define all-letters "abcdefghijklmnopqrstuvwxyz")
(define+ (update-letters)
(var letters (Array.prototype.join.call
(.. ($ ".word")
(map (lambda (index input-element)
(.. ($ input-element) (val)))))
""))
(.. ($ "#letters")
(text (.. (set-diff all-letters letters) (join "")))))
We've used define again to declare a private local variable
containing the alphabet. Then we use
define+, note the +, to define an external function
which does the work. The interior of the function is standard
jquery stuff: find the input elements by their class, collect their
values, concatenate them, use set-diff to find the leftover alphabet
letters, and then set the "letters"'s text to the result.(N.B. Gazelle's modules can scope both values and macros.
define-macro+ defines an external macro inside a module.)Now our module is complete. All that remains is to use it in
main.gazelle. Edit main.gazelle until it looks like this:(require
(("jquery/jquery" :all)
("hooves/hooves" :all)
("hooves/operator-functions" :all)
("three-words/three-words" :all))
($ (lambda ()
(window.set-interval update-letters 250))))
And then recompile it (C-c C-k). Gazelle can tell you've added a
module dependency and it can tell that that module needs to be
compiled. It takes care of it for you. Redirect your browser to the page you and you should be able to interactively type words into one of the three boxes and see the list of letters updated.
Here is an IFrame of the project running on my personal site, procyonic.
Deployment
Gazelle is meant to work in such a way that the resulting Javascript code can be deployed without any knowledge of Gazelle whatsoever. Simply copy the project to the place you want to host the page, and everything should work. The host does not need Emacs or any other Gazelle dependencies.Conclusions!
You can test out my version of the code here or look at the entire project in the examples directory of the Gazelle github.I've also started work on a manual for the Gazelle project, which should solidify the documentation significantly. I've used Gazelle to write a large amount of code at this point and I am sure that it could be used by other programmers meaningfully soon, so documentation is a major priority.
Thanks for reading!
PS - Finding three such words is impossible! See pangrams.
No comments:
Post a Comment