Browse Source

Got basic hot-reloading working!

* Run ./ then run runtime/HotReloadingTest, then
make changes to TextAdventure.cake and run
./ After, enter 'r' to reload the game. It's a
very simple example, but more good stuff is coming
* Hot-reloaded functions must use extern "C"
* Added additional error checking on command line options
Macoy Madson 2 years ago
  1. 6
  2. 4
  3. 3
  4. 10
  5. 3
  6. 65
  7. 4
  8. 6
  9. 32


@ -4,6 +4,6 @@
# jam -j4 && ./bin/cakelisp test/Dependencies.cake
# jam -j4 && ./bin/cakelisp test/Basic.cake
# Build Cakelisp itself, generate the runtime, build the runtime, then run the test
jam -j4 && ./bin/cakelisp runtime/TestMain.cake runtime/TextAdventure.cake \
&& cd runtime && jam -j4 && ./HotReloadingTest
# Build Cakelisp itself, generate the runtime, then build the runtime
jam -j4 && ./bin/cakelisp --enable-hot-reloading runtime/TestMain.cake runtime/TextAdventure.cake \
&& cd runtime && jam -j4


@ -0,0 +1,4 @@
./bin/cakelisp --enable-hot-reloading runtime/TextAdventure.cake \
&& cd runtime && jam -j4


@ -11,4 +11,5 @@ HotReloading.cake.cpp
Library TextAdventure : TextAdventure.cake.cpp ;
Main libGeneratedCakelisp$(SUFSHR) : TextAdventure.cake.cpp ;
LINKFLAGS on libGeneratedCakelisp$(SUFSHR) = -shared ;


@ -65,3 +65,13 @@
(PushBackAll (field output source) type-after-name-output)
(addLangTokenOutput (field output source) StringOutMod_EndStatement (addr invocation-token))
(return true))
(defmacro array-size ()
(destructure-arguments array-index)
(quick-token-at array-token array-index)
;; This should evaluate its argument, but I'm just hacking it in right now anyways
(unless (ExpectTokenType "array-size" array-token TokenType_Symbol)
(return false))
(tokenize-push output (/ (sizeof (token-splice (addr array-token)))
(sizeof (at 0 (token-splice (addr array-token))))))
(return true))


@ -7,7 +7,8 @@
(def-function-signature reload-entry-point-signature (&return bool))
(var hot-reload-entry-point-func reload-entry-point-signature nullptr)
(register-function-pointer (type-cast (addr hot-reload-entry-point-func) (* (* void)))
;; TODO Support name conversion at runtime (conversion requires tokens)
(unless (do-hot-reload)
(printf "error: failed to hot-reload\n")


@ -1,6 +1,67 @@
(c-import "<stdio.h>")
;; (import &comptime-only "Macros.cake")
(c-import "<stdio.h>"
"cctype" ;; For isdigit
"<stdlib.h>" ;; atoi
&with-decls "<vector>")
(defstruct room
name (* (const char))
description (* (const char))
connected-rooms (<> std::vector room))
(var rooms (const ([] room))
(array "front porch" "You're outside the front door of your home. The lights are off inside."
(array (array
"inside home" "Surprise! Your home is filled with cake. You look at your hands. You are cake."
(defun print-help ()
(printf "At any point, enter 'r' to reload the code, 'h' for help, or 'q' to quit\n")
(printf "Enter room number for desired room\n\n"))
(defun print-room (room-to-print (* (const room)))
(printf "You are at: %s.\n%s\n"
(path room-to-print > name)
(path room-to-print > description))
(var room-index int 0)
(for-in connected-room (& (const room)) (path room-to-print > connected-rooms)
(printf "%d: %s\n" room-index (field connected-room name))
(incr room-index))
(when (on-call (path room-to-print > connected-rooms) empty)
(printf "There are no connected rooms. This must be the end of the game!\n")))
;; Return true to hot-reload, or false to exit
(defun reloadable-entry-point (&return bool)
(printf "Cake Adventure\n")
(printf "CAKE ADVENTURE\n\n")
(var current-room (* (const room)) (addr (at 0 rooms)))
(print-room current-room)
(var input char 0)
(var previous-room (* (const room)) current-room)
(while (!= input 'q')
(when (!= current-room previous-room)
(set previous-room current-room)
(print-room current-room))
(printf "> ")
(set input (getchar))
(when (= input 'r')
(return true))
(when (= input 'h')
(when (call (in std isdigit) input)
(var room-request int (atoi (addr input)))
(var num-rooms int (on-call (path current-room > connected-rooms) size))
(unless (and num-rooms (< room-request num-rooms))
(printf "I don't know where that is. There are %d rooms\n" num-rooms)
(printf "Going to room %d\n" room-request)
(set current-room (addr (at room-request
(path current-room > connected-rooms))))))
(return false))


@ -190,9 +190,13 @@ struct EvaluatorEnvironment
// Ensure unique macro variable names, for example
int nextFreeUniqueSymbolNum;
// Used to load other files into the environment
// If this is null, it means other Cakelisp files will not be imported (which could be desired)
ModuleManager* moduleManager;
// Generate code so that objects defined in Cakelisp can be reloaded at runtime
bool enableHotReloading;
// Will NOT clean up macroExpansions! Use environmentDestroyInvalidateTokens()


@ -164,6 +164,12 @@ bool DefunGenerator(EvaluatorEnvironment& environment, const EvaluatorContext& c
if (!parseFunctionSignature(tokens, argsIndex, arguments, returnTypeStart))
return false;
if (environment.enableHotReloading)
addStringOutput(isModuleLocal ? output.source : output.header, "extern \"C\"",
StringOutMod_SpaceAfter, &tokens[startTokenIndex]);
// TODO: Hot-reloading functions shouldn't be declared static, right?
if (isModuleLocal)
addStringOutput(output.source, "static", StringOutMod_SpaceAfter, &tokens[startTokenIndex]);


@ -17,22 +17,27 @@ struct CommandLineOption
void printHelp(const CommandLineOption* options, int numOptions)
const char* helpString =
"OVERVIEW: Cakelisp transpiler\n"
"Cakelisp is a transpiler which generates C/C++ from a Lisp dialect.\n"
"Created by Macoy Madson <>.\n\n\n"
"OVERVIEW: Cakelisp transpiler\n\n"
"Cakelisp is a transpiler which generates C/C++ from a Lisp dialect.\n"
"Created by Macoy Madson <>.\n\n\n"
"USAGE: cakelisp [options] <input .cake files>\nAll options must precede .cake files.\n\n"
printf("%s", helpString);
for (int optionIndex = 0; optionIndex < numOptions; ++optionIndex)
printf("%s\n\t%s\n", options[optionIndex].handle, options[optionIndex].help);
printf(" %s\n %s\n\n", options[optionIndex].handle, options[optionIndex].help);
int main(int numArguments, char* arguments[])
bool enableHotReloading = false;
const CommandLineOption options[] = {
{"--enable-hot-reloading", &enableHotReloading,
"Generate code so that objects defined in Cakelisp can be reloaded at runtime"},
// Logging
{"--verbose-tokenization", &log.tokenization,
"Output details about the conversion from file text to tokens"},
{"--verbose-references", &log.references,
@ -44,8 +49,8 @@ int main(int numArguments, char* arguments[])
{"--verbose-build-process", &log.buildProcess,
"Output object statuses as they move through the compile-time pipeline"},
{"--verbose-processes", &log.processes,
"Output full command lines of all child processes created during the compile-time build "
"Output full command lines and other information about all child processes created during "
"the compile-time build process"},
{"--verbose-file-system", &log.fileSystem,
"Output why files are being written, the status of comparing files, etc."},
{"--verbose-metadata", &log.metadata, "Output generated metadata"},
@ -81,11 +86,23 @@ int main(int numArguments, char* arguments[])
return 1;
bool foundOption = false;
for (int optionIndex = 0; (unsigned long)optionIndex < ArraySize(options);
if (strcmp(arguments[i], options[optionIndex].handle) == 0)
*options[optionIndex].toggleOnOut = true;
foundOption = true;
if (!foundOption)
printf("Error: Unrecognized argument %s\n\n", arguments[i]);
printHelp(options, ArraySize(options));
return 1;
@ -104,6 +121,9 @@ int main(int numArguments, char* arguments[])
ModuleManager moduleManager = {};
if (enableHotReloading)
moduleManager.environment.enableHotReloading = true;
for (const char* filename : filesToEvaluate)
if (!moduleManagerAddEvaluateFile(moduleManager, filename))