Cakelisp news: file and line, RunProcess in C

By Macoy Madson. Published on .

I have been hard at work on my dynamic environment based on Tiny C Compiler. It has been going worse than I hoped. I am attempting to static link SDL 2 with an application I want to test the dynamic environment on. I am soldiering on, and hope to have something useful before the end of the month. If I can't get it by then, I may need to look for other options. It has been an uphill battle mainly because these tools were never intended to be used this way, but provide enough value that rewriting from scratch doesn't feel like a viable option for me.

Cakelisp itself has some new features worth mentioning.

File and line macros

I added (this-file) and (this-line) macros to Cakelisp's runtime macro library. These macros replace their invocation with the filename as a string or the Cakelisp line number, respectively.

The most obvious use-case for these is debugging, e.g. outputting:

MyFile.cake:1234: Hello

via something like:

(fprintf stderr "%s:%d: Hello\n" (this-file) (this-line))

The more interesting use-case is for code navigation straight from the program. I am writing a 2D vector animation program that uses the immediate-mode GUI paradigm for its UI. A button can be drawn to the screen and its click responded to in the following code:

(when (do-button renderer
        (addr (path regular-font > atlas))
        (path regular-font > texture)
        (+ (path state > ui-pane-position . X) 5) button-y 250 75
        "Toggle atlas")
  (set (path state > view-atlas) (not (path state > view-atlas))))

do-button will render the button and return true if the button was clicked.

I wanted to go straight from visible buttons on screen to the code that caused that button to exist. I added file and line arguments to do-button, then created a macro to automatically populate those fields whenever I called do-button. The do-button function then calls handle-ui-meta-inquiry, which opens Emacs for me at the file and line:

;; The UI element is trying to describe itself somehow. Do something helpful for the programmer
(defun handle-ui-meta-inquiry (element-name (addr (const char))
                               filename (addr (const char)) line int)
  (fprintf stderr "%s:%d: %s\n" filename line element-name)
  (var goto-line (array 32 char) (array 0))
  (snprintf goto-line (sizeof goto-line) "+%d" line)
      ("emacsclient" goto-line filename "-n")

This handle-ui-meta-inquiry would of course need to be fleshed out if I plan to ship it to end-users. Even this simple version enables a great quality-of-life improvement for me while I'm iterating on an interface. Now, if I want to make a change, I press a key while hovering over the button and I'm taken straight to the relevant code.

Note that I implemented the "meta inquiry" feature as an immediate mode function as well. I did not need to set up fancy metadata or describe my whole UI up-front. I can still extend the meta inquiry to render all the possible things I could inspect via building a list each frame instead of making the UI element decide whether it has been inquired about. These sorts of features are fantastic when making complex UIs.

This was made possible by the humble this-file and this-line macros!

RunProcess is now C-compatible

Cakelisp uses sub-processes to compile and link comptime and runtime code. I wrote several macros to make an easy interface to creating such processes in user Cakelisp code as well. One such macro is runtime-run-process-sequential-or, which I used in the previous section to open Emacs.

The macro has a simple form:

(runtime-run-process-sequential-or (command) on-failure)

The command uses a strict form where each argument is wrapped in quotes. This leaves no ambiguity for arguments with spaces, and allows you to freely intermix string variables with string literals in commands.

Here's a simple example:

    ("gcc" "-c" filename "-o" output
           :in-directory "my/build/dir")
  (fprintf stderr "Failed to compile %s\n" filename))

The sequential part of the name indicates that the current function will wait there until the process closes, thereby letting you easily run processes which have dependencies on the previous one. This is used frequently in GameLib to build 3rd party code during Cakelisp's compile-time phase.

There are start variants that start the process and continue execution immediately after for cases where you want to run processes in parallel instead.

This interface has been in for a while, and has proven its value. The recent change was to make the RunProcess file C instead of C++, which makes it compatible with more compilers (like Tiny C Compiler).

Needless to say, if you want to do more complex things like redirect pipes and so on, you'll need to use a different interface to run your sub-processes. However, in my use-cases these macros are more than enough.