~technomancy/fennel

Discussion of the Fennel programming language for contributors and users

https://fennel-lang.org

Patches

[PATCH] Allow inline macros.

Details
Message ID
<20181228055804.16653-1-phil@hagelb.org>
Download raw message
Patch +37 -23
This splits up `require-macros' into two parts:

* The bit that loads the macro modules has been split into a helper
  `loadMacros' function which is only concerned with that one job.

* Once we have a table of name/macro-function then we pass that to
  `doMacro', which takes each of the macro functions, turns them into
  specials, and inserts them into the specials for the given scope.

* doMacro can also be used on its own thru the new `macros' special
  which simply takes a table that gets evaluated in compiler scope.

For instance, from the test suite:

    (macros {:plus (fn [x y] `(+ x y))})

    (plus 9 9) ; -> 81

This allows macros to be used in a way that doesn't necessarily
require access to io.* functions, which will take care of
https://github.com/bakpakin/Fennel/issues/93

Documentation to follow in a subsequent commit if folks think this is
the right approach.
---
 fennel.lua | 58 +++++++++++++++++++++++++++++++++++-----------------------
 test.lua   |  2 ++
 2 files changed, 37 insertions(+), 23 deletions(-)

diff --git a/fennel.lua b/fennel.lua
index 4bbb5da..8ed248b 100644
--- a/fennel.lua
+++ b/fennel.lua
@@ -1898,37 +1898,49 @@ local function macroGlobals(env, globals)
     return allowed
 end
 
+local function doMacro(macros, ast, scope, parent)
+    assertCompile(isTable(macros), 'expected macros to be table', ast)
+    for k, v in pairs(macros) do
+        if allowedGlobals then table.insert(allowedGlobals, k) end
+        scope.specials[k] = macroToSpecial(v)
+    end
+end
+
+local function loadMacros(modname, scope, parent, ast)
+    local filename = assertCompile(searchModule(modname),
+                                   modname .. " not found.", ast)
+    local env = makeCompilerEnv(ast, scope, parent)
+    local globals = macroGlobals(env, currentGlobalNames())
+    return dofileFennel(filename, { env = env, allowedGlobals = globals })
+end
+
 SPECIALS['require-macros'] = function(ast, scope, parent)
-    for i = 2, #ast do
-        local modname = ast[i]
-        local mod
-        if macroLoaded[modname] then
-            mod = macroLoaded[modname]
-        else
-            local filename = assertCompile(searchModule(modname),
-                                           modname .. " not found.", ast)
-            local env = makeCompilerEnv(ast, scope, parent)
-            mod = dofileFennel(filename, {
-                env = env,
-                allowedGlobals = macroGlobals(env, currentGlobalNames())
-            })
-            macroLoaded[modname] = mod
-        end
-        for k, v in pairs(assertCompile(isTable(mod), 'expected ' .. modname ..
-                                        ' module to be table', ast)) do
-            if allowedGlobals then table.insert(allowedGlobals, k) end
-            scope.specials[k] = macroToSpecial(v)
-        end
+    assertCompile(#ast == 2, "Expected one module name argument", ast)
+    local modname = ast[2]
+    if not macroLoaded[modname] then
+        macroLoaded[modname] = loadMacros(modname, scope, parent, ast)
     end
+    doMacro(macroLoaded[modname], ast, scope, parent)
+end
+
+local function evalCompiler(ast, scope, parent)
+    local luaSource = compile(ast, { scope = makeScope(COMPILER_SCOPE) })
+    local loader = loadCode(luaSource, wrapEnv(makeCompilerEnv(ast, scope, parent)))
+    return loader()
+end
+
+SPECIALS['macros'] = function(ast, scope, parent)
+    assertCompile(#ast == 2, "Expected one table argument", ast)
+    local macros = evalCompiler(ast[2], scope, parent)
+    doMacro(macros, ast, scope, parent)
 end
 
 SPECIALS['eval-compiler'] = function(ast, scope, parent)
     local oldFirst = ast[1]
     ast[1] = sym('do')
-    local luaSource = compile(ast, { scope = makeScope(COMPILER_SCOPE) })
+    local val = evalCompiler(ast, scope, parent)
     ast[1] = oldFirst
-    local loader = loadCode(luaSource, wrapEnv(makeCompilerEnv(ast, scope, parent)))
-    loader()
+    return val
 end
 
 -- Load standard macros
diff --git a/test.lua b/test.lua
index b28b49b..7055b7f 100644
--- a/test.lua
+++ b/test.lua
@@ -312,6 +312,8 @@ local macro_cases = {
       x1"]=130,
     -- special form
     ["(reverse-it 1 2 3 4 5 6)"]=1,
+    -- inline macros
+    ["(macros {:plus (fn [x y] `(+ x y))}) (plus 9 9)"]=18,
 }
 
 print("Running tests for macro system...")
-- 
2.11.0