commit 5891d2801fc96a50e2f725ab300076e622704cd4
parent a24bb437a9063b0d9f51a75282495f5d191b67dc
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Thu, 21 May 2026 10:47:12 -0700
Update FRONTEND plan
Diffstat:
| M | doc/FRONTEND.md | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 66 insertions(+), 0 deletions(-)
diff --git a/doc/FRONTEND.md b/doc/FRONTEND.md
@@ -40,6 +40,72 @@ When the REPL calls `cfree_frontend_compile` for the second snippet, it passes a
This requires minimal changes to `CG` and `ObjBuilder`, preserves `CfreeCgSym == ObjSymId`, and guarantees monotonic uniqueness across the session.
+### 3. REPL Expression Modes
+`expr` and bare REPL input should become frontend compile modes, not
+driver-side source string rewriting. Today `driver/dbg.c` fabricates a C
+translation unit that defines a uniquely named zero-argument thunk, compiles it,
+looks up the thunk symbol, and calls it. That works for C syntax only and forces
+the driver to recover declarations from DWARF or JIT symbol metadata.
+
+In the persistent frontend model, `dbg` should keep the language-specific
+frontend instance alive and call the same `compile` entrypoint with an input
+kind describing how the source text should be interpreted:
+
+```c
+typedef enum CfreeFrontendInputKind {
+ CFREE_FRONTEND_INPUT_TRANSLATION_UNIT,
+ CFREE_FRONTEND_INPUT_REPL_TOPLEVEL,
+ CFREE_FRONTEND_INPUT_REPL_EXPR,
+ CFREE_FRONTEND_INPUT_REPL_BLOCK,
+} CfreeFrontendInputKind;
+
+typedef struct CfreeFrontendCompileOptions {
+ CfreeCodeOptions code;
+ CfreeDiagnosticOptions diagnostics;
+ const void *language_options;
+ CfreeFrontendInputKind input_kind;
+ const char *repl_entry_name; /* for REPL_EXPR / REPL_BLOCK */
+} CfreeFrontendCompileOptions;
+```
+
+- **`jit { ... }`** uses `CFREE_FRONTEND_INPUT_REPL_TOPLEVEL`: compile
+ language top-level declarations/statements into a fresh object while keeping
+ frontend state.
+- **`expr ...` and raw bare input** use `CFREE_FRONTEND_INPUT_REPL_EXPR`: the
+ frontend wraps the text as a zero-argument function named `repl_entry_name`
+ and returns an integer-compatible value (`uint64_t` / `i64`) for the debugger
+ to print.
+- **`expr { ... }`** uses `CFREE_FRONTEND_INPUT_REPL_BLOCK`: the frontend wraps
+ the body as a zero-argument function and lets the language define whether the
+ block must contain an explicit `return`.
+
+Each frontend owns its own wrapper spelling. C can lower an expression as:
+
+```c
+unsigned long long __cfree_dbg_expr_1(void) {
+ return (unsigned long long)(USER_EXPR);
+}
+```
+
+Toy can lower the same REPL request as:
+
+```toy
+fn __cfree_dbg_expr_1(): i64 {
+ return USER_EXPR as i64;
+}
+```
+
+Because the wrapper is compiled by the persistent frontend, it naturally sees
+macros, typedefs, functions, globals, and Toy declarations entered earlier in
+the session. `dbg` only needs to generate the unique entry name, compile the
+fresh object, append it to the JIT image, look up the entry symbol, and execute
+it through `CfreeJitSession`.
+
+DWARF recovery remains useful for inspecting preexisting objects and external
+debug info, but it should not be the primary mechanism for normal REPL
+expressions. The persistent frontend has strictly better source-level context
+for anything typed during the session.
+
### Alternative Discussed: DWARF Recovery
*Tradeoffs of recovering from DWARF instead of a stateful frontend:*
We *could* keep the frontend single-shot and recover types/symbols by reading DWARF from the JIT session (like LLDB does).