Skip to content
This repository has been archived by the owner on Sep 28, 2022. It is now read-only.

Creates a local anonymous macro #579

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/macros.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
Expand Down
25 changes: 25 additions & 0 deletions src/main/golo/gololang/ir/DSL.golo
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
89 changes: 89 additions & 0 deletions src/main/golo/gololang/macros.golo
Original file line number Diff line number Diff line change
Expand Up @@ -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

----
Expand Down Expand Up @@ -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)


3 changes: 3 additions & 0 deletions src/main/java/gololang/Predefined.java
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@ public static Map<Object, Object> map(Tuple... items) {
* ...
* }
* </code></pre>
* <p>See also the <a href="../../golo-guide.html#special_macros">Golo Guide</a>
*/
@Macro
public static GoloElement<?> special(GoloFunction fun) {
Expand All @@ -1026,6 +1027,8 @@ public static GoloElement<?> special(GoloFunction fun) {
* ...
* }
* </code></pre>
*
* <p>See also the <a href="../../golo-guide.html#contextual_macros">Golo Guide</a>
*/
@Macro
public static GoloElement<?> contextual(GoloFunction fun) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/gololang/Runtime.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,18 @@ public static CliCommand command() {
return command;
}

private static final ThreadLocal<GoloClassLoader> currentClassLoader = ThreadLocal.withInitial(Runtime::initClassLoader);

/**
* Returns the current thread class loader.
* <p>
* 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions src/test/java/org/eclipse/golo/compiler/MacroTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
4 changes: 4 additions & 0 deletions src/test/java/org/eclipse/golo/internal/testing/GoloTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions src/test/resources/for-macros/eval.golo
Original file line number Diff line number Diff line change
@@ -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()
}