Extending Hello World - blancas/eisen GitHub Wiki

The Eisen code passed to functions eisen, eisen= and eisenf should be:

  • One or more top-level definitions: module, import, declare, val, fun.
  • A single expression.

This also applies to the Eisen repl. When more than one top-level definitions are given, the repl will print the result of the last one.

Eisen code is translated to Clojure and evaluated immediately. Namespaces for Eisen code work just as in Clojure, using the module and import constructs for ns, use and require. Similarly, the placement of files in subdirectories must also follow Clojure usage.

Evaluation from the Host Application

The host application takes advantage of user-written code by:

  • Running start-up code from a well-known place.
  • Evaluating Eisen code at runtime to add and override values and functions.
  • Calling functions that were defined by Eisen code.

The namespace eisen.user should be used for start-up user code. The core function eisen-user is used to evaluate Eisen code from a well-known path. This function will look for a main function in that namespace. If no function eisen.user/main is found, no error is given.

The following code illustrates a Hello World host program designed to be extended, which is why is longer and more complex.

(use 'blancas.eisen.core)

;; To run from the REPL:
(comment
  (load-file "src/main/resources/hello/salute.clj")
)

;; Definitions.

(def eisen-file "src/main/resources/hello/salute.esn")

(def greeting "hello, %s!\n")  ;; Default form of the greeting.
(def subject "world")          ;; Default receiver of the greeting.

(def hook)                     ;; Something to do before the greeting.

(defn greet
  "Greets someone or something."
  [g x] (printf g x))

;; Extensions.

(init-eisen)         ;; Initialize Eisen.
(host-module user)   ;; Provide names to user code.
(eisenf eisen-file)  ;; Run user code from the well-known place.

;; Main program.

(when (bound? (var hook)) (hook))    ;; Run user-defined code.
(greet greeting subject)             ;; Greets the subject.

This program does two things to make itself extensible:

  • It defines its greeting and subject separately so they can be overridden.
  • Provides a hook to give user code a chance to run.

In order to make the program show anything other than hello, world the user must write some Eisen code in a file named by eisen-file. We want to ask first and then give a personalized greeting, so we write the following code:

module eisen.user

(* Asks for the name on behalf of the host app. *)

fun ask = do print "Who are you? ";
             flush;
             setq subject = readLine
          end

(* Sets the hook as the function ask. *)

setv hook = ask

The above code defines a function that prints a prompt asking who's the user; then sets the subject var to the result of reading what the user types. Next it sets the hook placeholder to the ask function, thus the program calls ask before making the greeting, after which the subject is the one the user typed.

Notice that the host's vars subject and hook were readily available to the Eisen code in its eisen.user namespace. That's because of the call to host-module in the host program, which will use the names from eisen.user to the host's namespace, in this case user.

Now we run the program at the Clojure repl.

(load-file "src/main/resources/hello/salute.clj")
Who are you? Elmo
hello, Elmo!

If we comment out the function main; quit and return to the Clojure repl.

(load-file "src/main/resources/hello/salute.clj")
hello, world!