Sunday, July 5, 2020

Walking Dream – single-player adventure game for the Oculus Quest

When debugging or executing a Clojure/Clojurescript program, I often want to execute functions in my program from the Clojure REPL. However, as much as I love the Clojure syntax for programming, it's pretty inconvenient for regularly executing commands in a program you've written.

For instance, I have a task tracking app written in Clojure. Every time I wanted to add a new todo item through the REPL, I used to have to type something like this:


> (a "iron curtains")

Item added.
NIL

> _

Though I was able to streamline the command by having the single-letter function name, this command remains incredibly awkward! What I really would prefer to type is this:


> a iron curtains

Syntax error compiling at (*cider-repl*:0:0).
Unable to resolve symbol: iron in this context
Syntax error compiling at (*cider-repl*:0:0).
Unable to resolve symbol: curtains in this context

Of course, since this shorter command does not obey the parsing rules of the Clojure reader, it just leads to syntax error in the REPL. This raises the question: Is there any way we can work around this limitation and enter custom commands into the Clojure REPL, using our own rules for parsing the command? The answer is... yes, sort of.

At this point, some of you may remember my classic Lisp tutorial, named Casting SPELs. In this tutorial, I implemented a text adventure game in a Common Lisp REPL, letting you enter commands such as "GET BOTTLE" and "WALK EAST". Naturally, the tutorial had to make compromises to work around REPL limitations, for the exact same reasons I'm outlining in this post... so as you can see, I've already had a decade-long love-hate relationship with inflexible Lisp REPL syntax.

Let's try to find a work-around to this issue once and for all!

Creating a Custom Emacs Hotkey

Certainly the Clojure REPL will never, ever accept "a iron curtains" as valid syntax in the reader, unless we're prepared to write our own custom fork of the Clojure programming language. But if we're using Emacs to run our REPL, we can do a little somethin' on the Emacs end of things, to wrap our command in a way that the Clojure reader will accept:


(defun wrap-clojure-command ()
    "takes a command written at the cider prompt and executes it as (repl-command \"...\")"
  (interactive)
  (move-beginning-of-line nil)
  (insert "(repl-command \"")
  (move-end-of-line nil)
  (insert "\")")
  (cider-repl-return))

(global-set-key "\M-z" 'wrap-clojure-command)

With this command, we can now enter custom commands into the Cider REPL, then hit the \M-z hotkey instead of ENTER to execute these commands. Then, the Emacs function wraps our REPL command within a standard Clojure string, inside a function call:


> a iron curtains 

           |
      Pressing \M+z
           |
           V

> (repl-command "a iron curtains")

To take advantage of this Emacs hotkey, all my Clojure/Clojurescript programs then have a function named repl-command that is then responsible for parsing my REPL string and performing the appropriate action.

Handling Namespaces

By default, Cider launches new REPL sessions in the user namespace. I like this default behavior, since it forces me to explicitly declare any namespaces I'm interacting with as I work in the repl. However, if I'm executing one of my custom repl commands, I don't want to have to deal with namespacing issues. Therefore, I have the following function declared in my personal Clojure libraries:


(defn user-repl-command [repl-command]
  (binding [*ns* (create-ns 'user)]
    (eval `(def user/repl-command ~command))))

This command takes an arbitrary function and declares it in the user namespace as user/repl-command. So at the bottom of the "core" module for each of my Clojure apps I can now declare my custom REPL command handler, to "register" it for use in the REPL:


(cl/user-repl-command my-repl-command)


Now I can immediately execute arbitrary commands for my Clojure app right in the Clojure REPL, using my own custom command syntax, even in the default cider REPL namespace!

Conclusion

Certainly, this is all very hacky, but I've used this workflow all day, every day, for about a year. This, alone, seems like a good enough reason for sharing my REPL workflow with others.

But certainly I'd love to come up with a less hackish solution to this problem. If any of you have ideas for cleaning up my worflow, please share them on twitter!



from Hacker News http://walkingdre.am/

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.