Working examples for embedding Wile (a Scheme interpreter) in Go applications.
- Go 1.23+
github.com/aalpar/wilev1.3.0+
go run ./cmd/ffi-basicsDemonstrates RegisterFunc with natural Go signatures:
| Go signature | Scheme | Shows |
|---|---|---|
func(int64) int64 |
(double 21) → 42 |
integer round-trip |
func(float64) float64 |
(circle-area 5.0) |
float64 |
func(string) string |
(greet "world") |
string |
func(int64) bool |
(even? 4) |
bool return |
func([]byte) int64 |
(byte-count #u8(1 2 3)) |
bytevector param |
func(Value) Value |
(identity '(1 2)) |
pass-through |
func(float64, float64) (float64, error) |
(safe-divide 10 0) |
error return |
func(string) |
(log-message "hi") |
void return |
func(...int64) int64 |
(sum 1 2 3) |
variadic |
func(string, ...string) string |
(join "-" "a" "b") |
prefix + variadic |
go run ./cmd/ffi-basicsDemonstrates RegisterFunc with composite Go types:
| Go type | Scheme representation | Direction |
|---|---|---|
[]int64 |
proper list '(1 2 3) |
Scheme → Go |
[]string |
proper list '("a" "b") |
Go → Scheme |
map[string]int64 |
hashtable | both directions |
struct{Name string; Age int64} |
alist '((Name . "Alice") (Age . 30)) |
both directions |
go run ./cmd/ffi-collectionsDemonstrates callback parameters and context.Context forwarding:
| Go signature | Scheme | Shows |
|---|---|---|
func(func(int64) int64, int64) int64 |
(apply-twice (lambda (x) (* x 2)) 3) |
basic callback |
func(func(int64), int64) |
(do-n-times (lambda (i) (display i)) 3) |
void callback |
func(context.Context) bool |
(has-deadline?) |
context forwarding |
func(context.Context, int64) int64 |
(ctx-double 21) |
context + args |
go run ./cmd/ffi-callbacksDemonstrates the full extension authoring pattern using a key-value store (kvstore/):
- Implements
registry.Extension(adds primitives to the registry) - Implements
registry.Closeable(cleanup onengine.Close()) - Stateful: the
*KVStoreholds amap[string]stringthat primitives read and write - Uses
machine.ForeignFunctionsignature withMachineContextfor argument access
Primitives provided:
| Primitive | Args | Description |
|---|---|---|
kv-set! |
2 | Set a key-value pair |
kv-get |
1-2 | Get by key, optional default |
kv-delete! |
1 | Delete a key |
kv-keys |
0 | List all keys (sorted) |
kv-count |
0 | Number of entries |
kv-clear! |
0 | Remove all entries |
go run ./cmd/custom-extensionSee kvstore/ for a complete example. The pattern is:
package myext
import "github.com/aalpar/wile/registry"
type MyExtension struct {
// your state here
}
func New() *MyExtension {
return &MyExtension{}
}
func (e *MyExtension) Name() string {
return "my-extension"
}
func (e *MyExtension) AddToRegistry(r *registry.Registry) error {
r.AddPrimitives([]registry.PrimitiveSpec{
// your primitives here
}, registry.PhaseRuntime)
return nil
}
// Optional: implement registry.Closeable for cleanup
func (e *MyExtension) Close() error {
return nil
}Load it:
engine, _ := wile.NewEngine(ctx, wile.WithExtension(myext.New()))RegisterFunc vs RegisterPrimitive: RegisterFunc accepts natural Go signatures
and handles type conversion automatically via reflection. RegisterPrimitive gives direct
access to MachineContext for argument handling. Use RegisterFunc for application-level
bindings; use RegisterPrimitive (via extensions) when you need fine-grained control.
Type mappings (RegisterFunc):
| Go type | Scheme type |
|---|---|
int64, int |
exact integer |
float64 |
inexact real |
string |
string |
bool |
boolean |
[]byte |
bytevector |
[]T |
proper list |
map[K]V |
hashtable |
struct |
alist ((Field . value) ...) |
func(...) |
procedure (lambda) |
wile.Value |
any Scheme value |
context.Context |
forwarded from VM (first param only) |
error |
runtime error (last return only) |