diff --git a/doc/macros.adoc b/doc/macros.adoc index 69a31f237..14e729ad3 100644 --- a/doc/macros.adoc +++ b/doc/macros.adoc @@ -211,6 +211,8 @@ $ golo golo --files app.golo As a consequence, a macro can't be used in the module that defines it. +However, the link:{golodoc}/gololang/macros.html#eval_3v[eval macro] allows to execute anonymous statements in the current module. + =============== [[substituting_macros]] diff --git a/src/main/golo/gololang/ir/DSL.golo b/src/main/golo/gololang/ir/DSL.golo index f699d8f21..2dbba165b 100644 --- a/src/main/golo/gololang/ir/DSL.golo +++ b/src/main/golo/gololang/ir/DSL.golo @@ -61,8 +61,33 @@ augment gololang.ir.GoloModule { } return this } + + ---- + Creates a submodule of the given module. + + See [`createSubmodule`](#createSubmodule_3v) + ---- + function submodule = |this, name, elements...| -> createSubmodule(this, name, elements) + } +---- +Creates a new module as an inner class of the given module. + +- *param* `parentModule`: the containing module +- *param* `submoduleName`: the base name of the new module +- *param* `elements`: the top-level golo elements to add to the new module +- *returns* a new `GoloModule` +---- +function createSubmodule = |parentModule, submoduleName, elements...| { + let submodule = `module(parentModule + : packageAndClass() + : createInnerClass(submoduleName)) + foreach elt in elements { + submodule: add(elt) + } + return submodule +} ---- Creates an IR [`import`](../../javadoc/gololang/ir/ModuleImport.html) node. diff --git a/src/main/golo/gololang/macros.golo b/src/main/golo/gololang/macros.golo index 564e3f4da..1602fd53e 100644 --- a/src/main/golo/gololang/macros.golo +++ b/src/main/golo/gololang/macros.golo @@ -15,6 +15,7 @@ This module defines the set of predefined macros. It is `&use`d by default. module gololang.macros import gololang.ir +import gololang.ir.DSL import gololang.macros.Utils ---- @@ -153,3 +154,91 @@ This is a toplevel macro. macro useOldstyleDestruct = |self| { self: enclosingModule(): metadata("golo.destruct.newstyle", false) } + + +---- +Anonymous macro with immediate evaluation. + +This macro generates a module with a macro containing the given statements, load it, and call the macro immediately. +The generated macro is +[contextual](../../javadoc/gololang/Predefined.html#contextual-gololang.ir.GoloFunction-) and +[special](../../javadoc/gololang/Predefined.html#special-gololang.ir.GoloFunction-), +and as such has two parameters: + +- `self`: representing the macro call itself +- `visitor`: representing the macro expansion visitor + +that can be used in the statements. + +Beware that this is not a closure, since the macro is defined in a separate module. The statements can't reference +elements defined in the calling module. + +For convenience, the generated module imports some modules useful while creating macros, namely: +- [`gololang.ir`](../../javadoc/gololang/ir/package-summary.html) +- [`gololang.ir.DSL`](./ir/DSL.html) +- [`gololang.ir.Quote`](./ir/Quote.html) +- [`gololang.macros.Utils`](./macros/Utils.html) + +For instance, the module +```golo +module Foo + +&eval { + let fn = map[ + ["answer", 42], + ["foo", "bar"], + ["hello", "world"] + ] + foreach name, value in fn: entrySet() { + self: enclosingModule(): add(`function(name): returns(constant(value))) + } +} +``` +will contain three functions, namely: +```golo +function answer = -> 42 +function foo = -> "bar" +function hello = -> "world" +``` +---- +@special +@contextual +macro eval = |self, visitor, statements...| { + let fname = gensym() + visitor: useMacroModule( + Runtime.load( + createSubmodule(self: enclosingModule(), gensym(), + `import("gololang.ir"), + `import("gololang.ir.DSL"), + `import("gololang.ir.Quote"), + `import("gololang.macros.Utils"), + `macro(fname) + : contextual(true) + : special(true) + : withParameters("self", "visitor") + : do(statements) + ) + ) + ) + return macroCall(fname) +} + + + +@special +@contextual +macro localMacros = |self, visitor, elements...| { + let parent = self: enclosingModule() + let submodule = createSubmodule(parent, "Macros", elements) + foreach elt in elements { + if elt: metadata("export") orIfNull false { + parent: add(elt) + } + } + Runtime.load(submodule) + visitor: useMacroModule(submodule: packageAndClass(): toString()) +} + +macro export = |m| -> m: metadata("export", true) + + diff --git a/src/main/java/gololang/Predefined.java b/src/main/java/gololang/Predefined.java index e9e758dd8..fe48c7631 100644 --- a/src/main/java/gololang/Predefined.java +++ b/src/main/java/gololang/Predefined.java @@ -1000,6 +1000,7 @@ public static Map map(Tuple... items) { * ... * } * + *

See also the Golo Guide */ @Macro public static GoloElement special(GoloFunction fun) { @@ -1026,6 +1027,8 @@ public static GoloElement special(GoloFunction fun) { * ... * } * + * + *

See also the Golo Guide */ @Macro public static GoloElement contextual(GoloFunction fun) { diff --git a/src/main/java/gololang/Runtime.java b/src/main/java/gololang/Runtime.java index baaa540b5..4f0913e2b 100644 --- a/src/main/java/gololang/Runtime.java +++ b/src/main/java/gololang/Runtime.java @@ -118,12 +118,18 @@ public static CliCommand command() { return command; } + private static final ThreadLocal currentClassLoader = ThreadLocal.withInitial(Runtime::initClassLoader); + /** * Returns the current thread class loader. *

* Possibly wrapped in a {@code GoloClassLoader} if necessary. */ public static GoloClassLoader classLoader() { + return currentClassLoader.get(); + } + + private static GoloClassLoader initClassLoader() { ClassLoader cl = Thread.currentThread().getContextClassLoader(); if (cl instanceof GoloClassLoader) { return (GoloClassLoader) cl; diff --git a/src/main/java/org/eclipse/golo/compiler/macro/MacroExpansionIrVisitor.java b/src/main/java/org/eclipse/golo/compiler/macro/MacroExpansionIrVisitor.java index 6cc9664dd..e81f739fa 100644 --- a/src/main/java/org/eclipse/golo/compiler/macro/MacroExpansionIrVisitor.java +++ b/src/main/java/org/eclipse/golo/compiler/macro/MacroExpansionIrVisitor.java @@ -273,6 +273,14 @@ private boolean tryExpand(FunctionInvocation invocation) { return expandRegularCalls && !invocation.isAnonymous() && !invocation.isConstant(); } + public MacroExpansionIrVisitor useMacroModule(Class cls) { + return useMacroModule(cls.getName()); + } + + public MacroExpansionIrVisitor useMacroModule(GoloModule mod) { + return useMacroModule(mod.getPackageAndClass().toString()); + } + public MacroExpansionIrVisitor useMacroModule(String name) { this.finder.addMacroClass(name); return this; diff --git a/src/test/java/org/eclipse/golo/compiler/MacroTest.java b/src/test/java/org/eclipse/golo/compiler/MacroTest.java index 46a061783..51ebbf8ec 100644 --- a/src/test/java/org/eclipse/golo/compiler/MacroTest.java +++ b/src/test/java/org/eclipse/golo/compiler/MacroTest.java @@ -90,4 +90,10 @@ public void withInit() throws Throwable { load("with-init-macros2"); run("with-init"); } + + @Test + public void eval() throws Throwable { + withClassLoader(gololang.Runtime.classLoader()); + run("eval"); + } } diff --git a/src/test/java/org/eclipse/golo/internal/testing/GoloTest.java b/src/test/java/org/eclipse/golo/internal/testing/GoloTest.java index a86d3f330..61bbab50e 100644 --- a/src/test/java/org/eclipse/golo/internal/testing/GoloTest.java +++ b/src/test/java/org/eclipse/golo/internal/testing/GoloTest.java @@ -44,6 +44,10 @@ public String filenameFor(String moduleName) { return RESOURCES + srcDir() + moduleName + ".golo"; } + public void withClassLoader(GoloClassLoader loader) { + this.loader = loader; + } + public abstract String srcDir(); @BeforeMethod diff --git a/src/test/resources/for-macros/eval.golo b/src/test/resources/for-macros/eval.golo new file mode 100644 index 000000000..338cdf0b3 --- /dev/null +++ b/src/test/resources/for-macros/eval.golo @@ -0,0 +1,34 @@ +module golo.test.EvalTest + +import org.hamcrest.MatcherAssert +import org.hamcrest.Matchers + +&eval { + let fn = map[ + ["answer", 42], + ["foo", "bar"], + ["hello", "world"] + ] + foreach name, value in fn: entrySet() { + self: enclosingModule(): add(`function(name): returns(constant(value))) + } +} + +&eval { + return `function("returned"): returns(constant("result")) +} + +function test_sideeffect = { + assertThat(answer(), `is(42)) + assertThat(foo(), `is("bar")) + assertThat(hello(), `is("world")) +} + +function test_returned = { + assertThat(returned(), `is("result")) +} + +function main = |args| { + test_sideeffect() + test_returned() +}