From 902ddbbfef9ac8528481b487ebaacf38025d64b1 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Thu, 10 Dec 2020 13:31:56 +0100 Subject: [PATCH 1/8] WIP --- src/main/golo/gololang/macros.golo | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/golo/gololang/macros.golo b/src/main/golo/gololang/macros.golo index 564e3f4da..94b9a1221 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,29 @@ This is a toplevel macro. macro useOldstyleDestruct = |self| { self: enclosingModule(): metadata("golo.destruct.newstyle", false) } + + +---- +Anonymous macro with immediate evaluation. +---- +@special +@contextual +macro eval = |self, visitor, statements...| { + let fname = gensym() + let mname = self: enclosingModule(): packageAndClass(): createInnerClass(gensym()) + visitor: useMacroModule(mname: toString()) + Runtime.load(`module(mname) + : `with( + `import("gololang.ir"), + `import("gololang.ir.DSL"), + `import("gololang.ir.Quote"), + `import("gololang.macros.Utils")) + : add(`macro(fname) + : contextual(true) + : special(true) + : withParameters("self", "visitor") + : do(statements))) + + return macroCall(fname) +} + From ae0fb1b4e97ea1a07a0173cc4c3fed4254ca978b Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Tue, 15 Dec 2020 23:26:40 +0100 Subject: [PATCH 2/8] doc --- src/main/golo/gololang/macros.golo | 20 ++++++++++++++++++++ src/main/java/gololang/Predefined.java | 3 +++ 2 files changed, 23 insertions(+) diff --git a/src/main/golo/gololang/macros.golo b/src/main/golo/gololang/macros.golo index 94b9a1221..59111e111 100644 --- a/src/main/golo/gololang/macros.golo +++ b/src/main/golo/gololang/macros.golo @@ -158,6 +158,26 @@ macro useOldstyleDestruct = |self| { ---- 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) ---- @special @contextual 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) { From f61943cc2c1ed2e1f92b3c469ebc9c9dabd8b5bb Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Tue, 15 Dec 2020 23:39:28 +0100 Subject: [PATCH 3/8] doc --- doc/macros.adoc | 2 ++ src/main/golo/gololang/macros.golo | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+) 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/macros.golo b/src/main/golo/gololang/macros.golo index 59111e111..68ccba038 100644 --- a/src/main/golo/gololang/macros.golo +++ b/src/main/golo/gololang/macros.golo @@ -178,6 +178,28 @@ For convenience, the generated module imports some modules useful while creating - [`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 From 0fdb798caf11e737b6be9932cf8704e56b06e169 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 18 Dec 2020 10:08:22 +0100 Subject: [PATCH 4/8] tests --- .../org/eclipse/golo/compiler/MacroTest.java | 5 +++ src/test/resources/for-macros/eval.golo | 34 +++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 src/test/resources/for-macros/eval.golo diff --git a/src/test/java/org/eclipse/golo/compiler/MacroTest.java b/src/test/java/org/eclipse/golo/compiler/MacroTest.java index 46a061783..00c4a0525 100644 --- a/src/test/java/org/eclipse/golo/compiler/MacroTest.java +++ b/src/test/java/org/eclipse/golo/compiler/MacroTest.java @@ -90,4 +90,9 @@ public void withInit() throws Throwable { load("with-init-macros2"); run("with-init"); } + + @Test + public void eval() throws Throwable { + run("eval"); + } } 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() +} From 5e63d636dc0f41ce22c474f94523ef0176b2c060 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 18 Dec 2020 10:10:22 +0100 Subject: [PATCH 5/8] First try to make tests pass There is an issue with classloaders while running the test module from java. It works when run directly by golo. Here I switch the current thread classloader. Works fine, but there should be a better approach (thread local singleton for `Runtime.classLoader()`. --- src/test/java/org/eclipse/golo/compiler/MacroTest.java | 5 +++++ .../java/org/eclipse/golo/internal/testing/GoloTest.java | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/src/test/java/org/eclipse/golo/compiler/MacroTest.java b/src/test/java/org/eclipse/golo/compiler/MacroTest.java index 00c4a0525..37d319e19 100644 --- a/src/test/java/org/eclipse/golo/compiler/MacroTest.java +++ b/src/test/java/org/eclipse/golo/compiler/MacroTest.java @@ -93,6 +93,11 @@ public void withInit() throws Throwable { @Test public void eval() throws Throwable { + GoloClassLoader gcl = gololang.Runtime.classLoader(); + ClassLoader ccl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(gcl); + withClassLoader(gcl); run("eval"); + Thread.currentThread().setContextClassLoader(ccl); } } 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 From f514efffa60cf78cbc512bf986f0c091fe5f6e85 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 18 Dec 2020 10:29:58 +0100 Subject: [PATCH 6/8] Better way to make tests pass Define a thread local variable to hold the current thread classloader. When running from golo, the current thread classloader will be a GoloClassLoader instance, so it is used as is. The issue is that when running from Java, the current classloader is not a golo one, so it was wrapped by `Runtime.classLoader()`. We therefore got a different one for each call. Keeping it in a thread local singleton resolve this issue. --- src/main/java/gololang/Runtime.java | 6 ++++++ src/test/java/org/eclipse/golo/compiler/MacroTest.java | 6 +----- 2 files changed, 7 insertions(+), 5 deletions(-) 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/test/java/org/eclipse/golo/compiler/MacroTest.java b/src/test/java/org/eclipse/golo/compiler/MacroTest.java index 37d319e19..51ebbf8ec 100644 --- a/src/test/java/org/eclipse/golo/compiler/MacroTest.java +++ b/src/test/java/org/eclipse/golo/compiler/MacroTest.java @@ -93,11 +93,7 @@ public void withInit() throws Throwable { @Test public void eval() throws Throwable { - GoloClassLoader gcl = gololang.Runtime.classLoader(); - ClassLoader ccl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(gcl); - withClassLoader(gcl); + withClassLoader(gololang.Runtime.classLoader()); run("eval"); - Thread.currentThread().setContextClassLoader(ccl); } } From 0575fb67e1aac26471dcb9fefbb65eea592c07e1 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 18 Dec 2020 15:42:44 +0100 Subject: [PATCH 7/8] Refactor submodule creation --- src/main/golo/gololang/ir/DSL.golo | 25 ++++++++++++++++ src/main/golo/gololang/macros.golo | 29 ++++++++++--------- .../macro/MacroExpansionIrVisitor.java | 8 +++++ 3 files changed, 48 insertions(+), 14 deletions(-) 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 68ccba038..98ba160b8 100644 --- a/src/main/golo/gololang/macros.golo +++ b/src/main/golo/gololang/macros.golo @@ -205,20 +205,21 @@ function hello = -> "world" @contextual macro eval = |self, visitor, statements...| { let fname = gensym() - let mname = self: enclosingModule(): packageAndClass(): createInnerClass(gensym()) - visitor: useMacroModule(mname: toString()) - Runtime.load(`module(mname) - : `with( - `import("gololang.ir"), - `import("gololang.ir.DSL"), - `import("gololang.ir.Quote"), - `import("gololang.macros.Utils")) - : add(`macro(fname) - : contextual(true) - : special(true) - : withParameters("self", "visitor") - : do(statements))) - + 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) } 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; From aac2c108fcdcdca484805f8b8b62df08d903e851 Mon Sep 17 00:00:00 2001 From: Yannick Loiseau Date: Fri, 18 Dec 2020 15:45:33 +0100 Subject: [PATCH 8/8] WIP: local module --- src/main/golo/gololang/macros.golo | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/golo/gololang/macros.golo b/src/main/golo/gololang/macros.golo index 98ba160b8..1602fd53e 100644 --- a/src/main/golo/gololang/macros.golo +++ b/src/main/golo/gololang/macros.golo @@ -223,3 +223,22 @@ macro eval = |self, visitor, 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) + +