Setting UpYou'll need GNU Emacs, which hosts the entire project.
You'll also need this repository and shadchen-el.
Then, in your emacs configuration, either
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
.emacsadd lines to the effect of:
If you want to use Gazelle you then must, at some point,
(push "~/emacs-code/shadchen-el/" load-path) (push "~/emacs-code/gazelle/" load-path)
(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.elfrom the Gazelle repository contains code for doing that for Gazelle. Shadchen is just one file,
shadchen.el, and is easier to byte compile.
Our GoalMy 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 StartedFirst create a new directory for your project, eg:
Then, inside that directory, create a
# mkdir -p src/gazelle/three-words # cd src/gazelle/three-words
scriptsdirectory. We want to create symbolic links to the Gazelle standard library, called
hoovesand to the Gazelle stub library that allows you to use jquery. When you deploy the project, you'll use
cp -rLto copy the contents of those directories instead of the symbolic links.
You'll also need
# mkdir scripts && cd scripts # ln -s $GAZELLE_PATH/scripts/hooves hooves # ln -s $GAZELLE_PATH/scripts/jquery jquery
require.js, which you can link to in the Gazelle repository:
Finally, create a
# ln -s $GAZELLE_PATH/scripts/require.js require.js
main.gazellefile. This will be our entry point.
We'll come back to
# touch main.gazelle
main.gazellein a second, but first we have to make our page. Go up one directory, to the project directory, and create an
index.htmlfile and fill it in with this:
Absolutely critical here is:
<!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>
This is the
<script data-main="scripts/main.js" src="scripts/require.js"></script>
require.jsentry point, which tells the browser that
main.js, which will be generated from
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
Ok! Our HTML page is set up, now open up
main.gazellein your browser.
main.gazelleGazelle does not provide default definitions of functions corresponding to operators, like
<, etc. So the first thing we need to do is
requirethe modules from
hoovesthat define operator functions.
require.jsis 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/hooves, which defines utility functions and macros.
:allafter 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
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
scriptsdirectory, 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.htmland 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 ProblemOk, 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 a
set-difffunction. 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:
This code is a straightforward module. The first form is the same as the first form in a
(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))
require, it indicates that in this module we depend on and use the operator functions and the hooves module, as well as jquery.
defineform 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
set1that 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
textof 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 "")))))
defineagain 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
jquerystuff: find the input elements by their class, collect their values, concatenate them, use
set-diffto find the leftover alphabet letters, and then set the "letters"'s
textto 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.gazelleuntil it looks like this:
And then recompile it (
(require (("jquery/jquery" :all) ("hooves/hooves" :all) ("hooves/operator-functions" :all) ("three-words/three-words" :all)) ($ (lambda () (window.set-interval update-letters 250))))
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.
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.