Eval
Overview
The eval
function in Gerbil Scheme is a powerful tool for dynamic code execution. However, its behavior and usage differ between runtime and compile-time, and it has some key differences compared to Gambit Scheme. This document outlines how to use eval
effectively in various scenarios.
Runtime Behavior
Interactive Environment
In the Gerbil interpreter (gxi
), eval
works similarly to other Scheme implementations:
> (def greeting "hello world")
> (eval '(displayln greeting))
hello world
Compiled Binaries
When using eval
in compiled binaries, additional steps are necessary.
To demonstrate this let's slightly modify code from previous example, save it into the file:
eval-example.ss
(export main)
(def greeting "hello world")
(def (main . args)
(eval '(displayln greeting)))
Then compile into executable and run:
$ gxc -exe eval-example.ss
...
$ ./eval-example
*** ERROR -- Unbound variable: greeting
--- continuation backtrace:
[0] ##primordial-exception-handler-hook
An error is reported because eval operates on top-level namespace while greeting
is defined a module namespace which is added implicitly.
This may be fixed by adding a namespace to greeting
:
(export main)
(def greeting "hello world")
(def (main . args)
(eval '(displayln eval-example#greeting))
$ gxc -exe eval-example.ss
...
$ ./eval-example
hello world
Adding namespace to each symbol referenced under eval
may be a bit tedious, there is a better way to do this with (extern ...)
form.
Eval expression may be modified to look like this:
(begin
(extern namespace: eval-example
greeting)
(displayln greeting))
Using extern
will make eval-example#greeting
available as greeting
at runtime. But there is one more thing to do before it will work without errors, because extern
relies on :gerbil/expander
which should be loaded. In interactive mode this happens implicitly, while inside compiled binaries expander need to be loaded manually. Here is the final version:
(import :gerbil/expander)
(export main)
(def greeting "hello world")
(def (main . args)
(gerbil-load-expander!)
(eval '(begin
(extern namespace: eval-example
greeting)
(displayln greeting))))
Which works as expected:
$ gxc -exe eval-example.ss
...
$ ./eval-example
hello world
Use case: loading a configuration file at run-time.
Suppose you'd like to use scheme as the configuration language for your program. For the sake of simplicity, let's assume that the config file is $HOME/.config/eval-test/config.scm
.
Let's create a project like this:
- guide
- gerbil.pkg
- build.ss
- eval
- main.ss
- lib.ss
In eval/main.ss
, there is a call to (eval (include ...) ...)
.
(import :gerbil/expander
./lib)
(export main)
(def (main . args)
(gerbil-load-expander!)
(eval '(include "~/.config/eval-test/config.scm")))
If the configuration file only needs symbols from your main program, then you already know what to do. But if it also needs functions from a library use provide, then you'll need to find the correct namespace. Let's define the library, eval/lib.ss
:
(export #t)
(def (greeting)
(displayln "Hello from module!"))
The exact namespace name can be determined in multiple ways, I'll show one.
Inside gerbil.pkg
, there is this:
(package: guide)
This means that the root of the namespace is 'guide'. The library file was eval/lib.ss
, so the namespace name is guide/eval/lib
.
This is build.ss
so that you can replicate the project easier.
#!/usr/bin/env gxi
;;; -*- Gerbil -*-
(import :std/build-script)
(defbuild-script
'("eval/lib"
(exe: "eval/main" bin: "eval")))
The config file (~/.config/eval-test/config.scm
) will look like this:
(extern namespace: guide/eval/lib
greeting)
(greeting)
Tips and tricks
Sometimes, you can use quasiquotation in eval to "smuggle" variables into the code, to avoid having to define externs:
> (let ((a 1))
> (eval `(+ ,a 2)))
3