Just a test for a macro to allow overloading existing functions! (actually, a multimethod generic dispatch)
NOTE: this is just a POC to eat my own dog food...
module A
function foo = |a, b, c| {
return "A::foo"
}
module B
&:overload(^A::foo, a oftype java.lang.String.class) {
|a, b, c| -> "B::foo_1"
}
&:overload(^A::foo, b < 10) {
|a, b, c| -> "B::foo_2"
}
module Main
import B
&:overload(^B::foo, c != "c") {
|a, b, c| -> "Main::foo"
}
function main = |args| {
require(foo(1, 42, "c") == "A::foo", "err")
require(foo("a", 5, "c") == "B::foo_1", "err")
require(foo(1, 5, "c") == "B::foo_2", "err")
require(foo(1, 42, "bar") == "Main::foo", "err")
println("ok")
}
as an example, module B
expends to
module B
local function __$$_closure_0 = |a, b, c| {
return "B::foo_1"
}
local function __$$_closure_1 = |a, b, c| {
return "B::foo_2"
}
function foo = |a, b, c| -> match
when a oftype java.lang.String.class then ^__$$_closure_0: invoke(a, b, c)
when b < 10 then ^__$$_closure_1: invoke(a, b, c)
otherwise ^A::foo: invoke(a, b, c)
}
and the magic:
module Overload
import gololang.macros.CodeBuilder
import gololang.macros.Utils
local function getFunctionByName = |mod, name| {
foreach f in mod: getFunctions() {
if f: getName() == name {
return f
}
}
return null
}
local function getParameterNames = |closureRef| {
return closureRef: getTarget(): getParameterNames()
}
local function createDelegate = |func, closureRef| {
let delegate = methodInvocation("invoke")
foreach n in getParameterNames(closureRef) {
delegate: arg(refLookup(n))
}
return methodCall(func, delegate)
}
local function createMainFunction = |func, condition, closureRef| {
let mainBuilder = publicFunction(): name(func: getValue(): name())
foreach n in getParameterNames(closureRef) {
mainBuilder: param(n)
}
mainBuilder: block(
returns(matching()
: whenValue(condition, createDelegate(closureRef, closureRef))
: otherwiseValue(createDelegate(func, closureRef))
))
return mainBuilder: build()
}
local function insertElseIf = |condBlock, condition, closureRef| {
# This is ugly and fragile... must refactor the IR to keep a match node...
let ref = condBlock: getStatements(): get(0): getLocalReference()
let cond = condBlock: getStatements(): get(1)
let value = createDelegate(closureRef, closureRef)
let newBranch = branch(): condition(condition)
: whenTrue(block(assignment(false, ref, value)))
: whenFalse(cond: getFalseBlock())
: build()
cond: setFalseBlock(null)
cond: setElseConditionalBranching(newBranch)
relinkReferenceTables(newBranch, condBlock)
}
function overload = |context, func, condition, block| {
let closureRef = block: getStatements(): get(0)
var mainFunction = getFunctionByName(context, func: getValue(): name())
if mainFunction is null {
context: addFunction(createMainFunction(func, condition, closureRef))
} else {
let matchBlock = mainFunction: getBlock(): getStatements(): get(0): getExpressionStatement()
insertElseIf(matchBlock, condition, closureRef)
}
}