~technomancy/fennel

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch

[PATCH] Allow inline macros.

Details
Message ID
<20181228055804.16653-1-phil@hagelb.org>
Sender timestamp
1545976684
DKIM signature
missing
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