Skip to content

Commit

Permalink
macros as lazy functions in a happy place now
Browse files Browse the repository at this point in the history
  • Loading branch information
billhails committed Oct 27, 2024
1 parent 3ba59a7 commit 63ac30c
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 9 deletions.
45 changes: 45 additions & 0 deletions docs/OPERATORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,48 @@ local scoping rules, since `AND` is now a normal function then free variables in
body are evaluated in the context of the function definition, and variables in the
argument expressions are evaluated in the calling context.

Got that working, and we're also handling local shadowing of arguments so they don't
get wrapped in an invocation unless they are the same lexical variable.

One little unnecssary inefficiency needs to be addressed. If one macro calls another,
for example

```
macro NAND(a, b) { NOT(AND(a, b)) }
```

This first gets rewritten, by `lambda_conversion.c` to

```
fn NAND(a, b) { NOT(AND(fn () {a}, fn () {b})) }
```

and then subsequently by `macro_substitution.c` to

```
fn NAND(a, b) { NOT(AND(fn () {a()}, fn () {b()})) }
```

While correct, the expression `fn () {a()}` is just `a` so we'll need
a pass to optimise away this unnecessary wrapping and unwrapping,
essentially restoring

```
fn NAND(a, b) { NOT(AND(a, b)) }
```

Two approaches:

1. Macro specific, have a special type for macro argument application
and another for macro argument wrapping, and detect the explicit
combination of the two.
2. Generic pass that would detect this wherever it occurs and optimize it.

In either case we need to be a little bit careful that we allow the
pattern if the argument is being modified, for example if a macro
called another with it's argument modified in some way then the pattern
i.e. `fn() { a() + 1 }` would be necessary.

Got option 1 working, but no need for extra types, just inspect the
thunk during macro conversion, if it has no arguments and just contains
a symbol that would otherwise be invoked then return the symbol.
23 changes: 14 additions & 9 deletions src/macro_substitution.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,21 @@
# include "debugging_off.h"
#endif

static LamLam *performLamSubstitutions(LamLam *lam, LamMacroArgsTable *symbols) {
static bool isReplacementSymbol(HashSymbol *var, LamMacroArgsTable *symbols) {
return getLamMacroArgsTable(symbols, var, NULL);
}

static LamExp *performLamSubstitutions(LamLam *lam, LamMacroArgsTable *symbols) {
ENTER(performLamSubstitutions);
// fn () { a() } == a
if ( lam->args == NULL
&& lam->exp->type == LAMEXP_TYPE_VAR
&& isReplacementSymbol(lam->exp->val.var, symbols)) {
return lam->exp;
}
lam->exp = lamPerformMacroSubstitutions(lam->exp, symbols);
LEAVE(performLamSubstitutions);
return lam;
}

static bool isReplacementSymbol(HashSymbol *var, LamMacroArgsTable *symbols) {
return getLamMacroArgsTable(symbols, var, NULL);
return newLamExp_Lam(CPI(lam), lam);
}

static bool containsReplacementSymbols(LamVarList *vars, LamMacroArgsTable *symbols) {
Expand Down Expand Up @@ -374,8 +380,7 @@ LamExp *lamPerformMacroSubstitutions(LamExp *exp, LamMacroArgsTable *symbols) {
case LAMEXP_TYPE_CONSTRUCTOR:
break;
case LAMEXP_TYPE_LAM:
exp->val.lam =
performLamSubstitutions(exp->val.lam, symbols);
exp = performLamSubstitutions(exp->val.lam, symbols);
break;
case LAMEXP_TYPE_VAR: {
LamExp *rep = performVarSubstitutions(CPI(exp), exp->val.var, symbols);
Expand Down Expand Up @@ -445,7 +450,7 @@ LamExp *lamPerformMacroSubstitutions(LamExp *exp, LamMacroArgsTable *symbols) {
exp->val.lookup = performLookupSubstitutions(exp->val.lookup, symbols);
break;
default:
cant_happen("unrecognized LamExp type %s", lamExpTypeName(exp->type));
cant_happen("unrecognized %s", lamExpTypeName(exp->type));
}
}
LEAVE(lamPerformMacroSubstitutions);
Expand Down

0 comments on commit 63ac30c

Please sign in to comment.