Sunday, February 10, 2013

Covering the Alphabet: A Complete Gazelle Use Example

Today I'll be demonstrating how one can set up a complete project in Gazelle, including the use of the module system. In the last month I've been developing a simple game using Gazelle and Gazelle has developed significantly during that process. Things have finally settled down enough that a demonstrating is meaningful.

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 a set-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: