Skip to content

Instantly share code, notes, and snippets.

@yloiseau
Last active August 29, 2015 14:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yloiseau/ba0301730a29dbe13193 to your computer and use it in GitHub Desktop.
Save yloiseau/ba0301730a29dbe13193 to your computer and use it in GitHub Desktop.
multimethod macro

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)
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment