Skip to content

Commit

Permalink
Add (macroexpand ...) and update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
cryptocode committed Nov 18, 2022
1 parent 70af85f commit 6ea44fd
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 9 deletions.
53 changes: 50 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,11 @@ A lambda invocation has its own environment, and the parent environment is the o

### macro

This function creates a macro expression. Unlike lambdas, arguments are not evaluated when the macro is invoked. Instead, they're evaluated if and when the body does so, usually using quasi quotation.
This function creates a macro.

When executed, the body is evaluated, usually producing a quasiquote expression (which serves as a code template). The last expression is then evaluated again as the result. This causes the quasiquote expansion to be evaluated as code:
Unlike lambdas, arguments are not evaluated when the macro is invoked. Instead, they're evaluated if and when the body does so.

When the macro is invoked, the body is evaluated. The returned expression (which represents Bio code) is then evaluated as the final result.

```scheme
(var print-with-label (macro (label &rest values)
Expand All @@ -389,7 +391,52 @@ When executed, the body is evaluated, usually producing a quasiquote expression
Primes: 2 3 5 7
```

A macro invocation has its own environment, and the parent environment is the current one.
A macro invocation has its own environment, and the parent environment is the current one. This is different from lambdas whose parent environment is the one in which the lambda was defined.

### macroexpand

You can stop the evaluation of the code returned from a macro by wrapping it in `macroexpand`

Consider a typical swap macro:

```scheme
(var swap (macro (a b)
`(let ((temp ,a))
(set! ,a ,b)
(set! ,b temp))))
```

Here's a typical usage example:

```scheme
(let ((x 3) (y 7))
(swap x y)
(print "Swapped:" x y "\n")
)
Swapped: 7 3
```

But now we wanna see how the macro is expanded as code instead:

```scheme
(let ((x 5) (y 8))
(print "Macro expansion:" (macroexpand (swap x y)) "\n")
)
Macro expansion: (let ((temp x)) (set! x y) (set! y temp))
```

Of course, you can store away the expansion for later invocation, or just evaluate the expansion directly:

```scheme
(let ((x 5) (y 8))
(eval (macroexpand (swap x y)))
(print "Swapped:" x y "\n")
)
Swapped: 8 5
```

### self and environment lookups

Expand Down
8 changes: 4 additions & 4 deletions src/ast.zig
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,10 @@ pub fn makeAtomAndTakeOwnership(literal: []const u8) anyerror!*Expr {
/// Make and return a potentially interned atom (symbol or number)
fn makeAtomImplementation(literal: []const u8, take_ownership: bool) anyerror!*Expr {
const intrinsic_atoms: []const *Expr = &.{
&intrinsics.expr_atom_quasi_quote, &intrinsics.expr_atom_quote, &intrinsics.expr_atom_unquote, &intrinsics.expr_atom_unquote_splicing, &intrinsics.expr_atom_list,
&intrinsics.expr_atom_if, &intrinsics.expr_atom_cond, &intrinsics.expr_atom_begin, &intrinsics.expr_atom_nil, &intrinsics.expr_atom_rest,
&intrinsics.expr_atom_mut, &intrinsics.expr_atom_true, &intrinsics.expr_atom_false, &intrinsics.expr_atom_last_eval, &intrinsics.expr_atom_last_try_err,
&intrinsics.expr_atom_last_try_value, &intrinsics.expr_atom_break,
&intrinsics.expr_atom_quasi_quote, &intrinsics.expr_atom_quote, &intrinsics.expr_atom_unquote, &intrinsics.expr_atom_unquote_splicing, &intrinsics.expr_atom_list,
&intrinsics.expr_atom_if, &intrinsics.expr_atom_cond, &intrinsics.expr_atom_begin, &intrinsics.expr_atom_nil, &intrinsics.expr_atom_rest,
&intrinsics.expr_atom_mut, &intrinsics.expr_atom_true, &intrinsics.expr_atom_false, &intrinsics.expr_atom_last_eval, &intrinsics.expr_atom_last_try_err,
&intrinsics.expr_atom_last_try_value, &intrinsics.expr_atom_break, &intrinsics.expr_atom_macroexpand,
};

// Lazy initialization of the interned intrinsics map
Expand Down
13 changes: 12 additions & 1 deletion src/interpreter.zig
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ pub const Interpreter = struct {
pub fn eval(self: *Interpreter, environment: *Env, expr: *Expr) anyerror!*Expr {
var maybe_next: ?*Expr = expr;
var env: *Env = environment;
var seen_macro_expand = false;

tailcall_optimization_loop: while (maybe_next) |e| {
if (self.exit_code) |_| {
Expand Down Expand Up @@ -209,7 +210,12 @@ pub const Interpreter = struct {
}

const args_slice = list.items[1..];
if (list.items[0] == &intrinsics.expr_atom_begin) {
if (list.items[0] == &intrinsics.expr_atom_macroexpand) {
// Signal that we don't want to evaluate the expression returned by the macro
seen_macro_expand = true;
maybe_next = args_slice[args_slice.len - 1];
continue;
} else if (list.items[0] == &intrinsics.expr_atom_begin) {
var res: *Expr = &intrinsics.expr_atom_nil;
for (args_slice[0 .. args_slice.len - 1]) |arg| {
res = try self.eval(env, arg);
Expand Down Expand Up @@ -375,6 +381,11 @@ pub const Interpreter = struct {
try self.printError(err);
return &intrinsics.expr_atom_nil;
};

if (seen_macro_expand) {
return result;
}

env = local_env;
maybe_next = result;
continue;
Expand Down
3 changes: 2 additions & 1 deletion src/intrinsics.zig
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub var expr_atom_nil = Expr{ .val = ExprValue{ .sym = "nil" } };
pub var expr_atom_rest = Expr{ .val = ExprValue{ .sym = "&rest" } };
pub var expr_atom_mut = Expr{ .val = ExprValue{ .sym = "&mut" } };
pub var expr_atom_break = Expr{ .val = ExprValue{ .sym = "&break" } };
pub var expr_atom_macroexpand = Expr{ .val = ExprValue{ .sym = "macroexpand" } };
pub var expr_std_math_pi = Expr{ .val = ExprValue{ .num = std.math.pi } };
pub var expr_std_math_e = Expr{ .val = ExprValue{ .num = std.math.e } };
pub var expr_std_import = Expr{ .val = ExprValue{ .fun = stdImport } };
Expand Down Expand Up @@ -1136,7 +1137,7 @@ pub fn stdMacro(ev: *Interpreter, _: *Env, args: []const *Expr) anyerror!*Expr {
pub fn stdEval(ev: *Interpreter, env: *Env, args: []const *Expr) anyerror!*Expr {
var res: *Expr = &expr_atom_nil;
for (args) |arg| {
if (arg.val == ExprType.lst and (arg.val.lst.items[0] == &expr_atom_quote or arg.val.lst.items[0] == &expr_atom_quasi_quote)) {
if (arg.val == ExprType.lst and (arg.val.lst.items[0] == &expr_atom_quote or arg.val.lst.items[0] == &expr_atom_quasi_quote or arg.val.lst.items[0] == &expr_atom_macroexpand)) {
res = try ev.eval(env, try ev.eval(env, arg));
} else {
res = try ev.eval(env, arg);
Expand Down

0 comments on commit 6ea44fd

Please sign in to comment.