Last active
March 19, 2020 06:16
-
-
Save rayman22201/bfc4ce11fa15e57176130c4855407199 to your computer and use it in GitHub Desktop.
lift boolean procs from basic types to component wise containers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fenv | |
import macros | |
import sequtils | |
import strformat | |
# afaik we need this to get the types. I can't think of another way. | |
{.experimental: "dynamicBindSym".} | |
type | |
Vec*[N: static[int]] = array[N, float32] | |
Vec2* = Vec[2] | |
Vec3* = Vec[3] | |
Vec4* = Vec[4] | |
macro genComponentWise*(op, vecSym: untyped): untyped = | |
# op is a nnkOpenSymChoice. A.K.A a list of possible procs that match. | |
# We haven't type checked yet, so we don't know which symbol we actually need. | |
# Nim just gives us a list of all possible choices. | |
# We can try to search the list and poor mans type check ourselves | |
let opSym = op[0].bindSym | |
let vecType = vecSym.bindSym.getImpl | |
let baseOp = if opSym.kind != nnkSym: | |
let baseType = vecType[2][2] | |
var matchingOpIndex = -1 | |
for i in 0 ..< opSym.len: | |
if opSym[i].getImpl[3][1][2] == baseType: | |
matchingOpIndex = i | |
break | |
if matchingOpIndex == -1: | |
error(fmt"no matching operation {op[matchingOpIndex]} found for {baseType}") | |
opSym[matchingOpIndex].getImpl | |
else: | |
opSym.getImpl | |
# generate formal params. | |
var formalP : seq[NimNode] | |
formalP.add(baseOp[3][0]) # same return type as base op | |
for i in 1 ..< baseOp[3].len: | |
if baseOp[3][i][baseOp[3][i].len - 1].kind == nnkEmpty: | |
let param = baseOp[3][i].copyNimTree() | |
param[param.len - 2] = vecSym | |
formalP.add(param) | |
else: | |
# We have to reconstruct the param node so it doesn't match old env symbols | |
formalP.add(newIdentDefs(baseOp[3][i][0].strVal.ident, baseOp[3][i][1], baseOp[3][i][2])) | |
# generate a macro with a generic param and all the necessary normal params (including possible extra ones). | |
let macroName = genSym(nskMacro, "genComponentWiseImpl") | |
let macroImpl = nnkMacroDef.newTree( | |
nnkPostfix.newTree( | |
newIdentNode("*"), | |
macroName | |
), | |
newEmptyNode(), | |
nnkGenericParams.newTree( | |
nnkIdentDefs.newTree( | |
newIdentNode("N"), | |
nnkBracketExpr.newTree( | |
newIdentNode("static"), | |
newIdentNode("int") | |
), | |
newEmptyNode() | |
) | |
) | |
) | |
let formalMacroParams = nnkFormalParams.newTree(baseOp[3][0]) # same return type as base op | |
for i in 1 ..< baseOp[3].len: | |
if baseOp[3][i][baseOp[3][i].len - 1].kind == nnkEmpty: | |
let param = baseOp[3][i].copyNimTree() | |
param[param.len - 2] = nnkBracketExpr.newTree(vecSym, ident"N") | |
formalMacroParams.add(param) | |
else: | |
# We have to reconstruct the param node so it doesn't match old env symbols | |
formalMacroParams.add(newIdentDefs(baseOp[3][i][0].strVal.ident, ident"untyped")) | |
macroImpl.add(formalMacroParams) | |
macroImpl.add(newEmptyNode()) | |
macroImpl.add(newEmptyNode()) | |
# generate macro body. And all the baseOps together for 0..N | |
#let checkImpl = nnkCall.newTree( | |
# op, | |
# nnkBracketExpr.newTree(ident"v1", newLit(i)), | |
# nnkBracketExpr.newTree(ident"v2", newLit(i)) | |
#) | |
let indexIdent = ident"i" | |
let checkImpl = nnkCall.newTree( | |
nnkDotExpr.newTree( | |
newIdentNode("nnkCall"), | |
newIdentNode("newTree") | |
), | |
nnkCall.newTree( | |
newIdentNode("ident"), | |
newStrLitNode($op), | |
)) | |
for i in 1 ..< baseOp[3].len: | |
if baseOp[3][i][baseOp[3][i].len - 1].kind == nnkEmpty: | |
# the last 2 children of a formal param list are the type and default param. | |
# the rest are the param names. | |
for j in 0 ..< baseOp[3][i].len - 2: | |
checkImpl.add(nnkCall.newTree( | |
nnkDotExpr.newTree( | |
newIdentNode("nnkBracketExpr"), | |
newIdentNode("newTree") | |
), | |
baseOp[3][i][j], | |
nnkCall.newTree( | |
newIdentNode("newLit"), | |
indexIdent | |
) | |
)) | |
else: | |
for j in 0 ..< baseOp[3][i].len - 2: | |
checkImpl.add(baseOp[3][i][j]) | |
let macroBody = quote do: | |
for `indexIdent` in countdown(N-1, 0): | |
let check = `checkImpl` | |
if result.kind == nnkEmpty: | |
result = check | |
else: | |
result = nnkInfix.newTree(ident("and"), check, result) | |
macroImpl.add(macroBody) | |
let macroCall = nnkCall.newTree(macroName) | |
for i in 1 ..< baseOp[3].len: | |
for j in 0 ..< baseOp[3][i].len - 2: | |
macroCall.add(baseOp[3][i][j]) | |
let procDef = newProc( | |
op, # same name as base op | |
formalP, | |
macroCall | |
) | |
result = nnkStmtList.newTree(macroImpl, procDef) | |
echo result.repr | |
# vector component-wise <, <= predicates | |
genComponentWise(`<`, Vec) | |
genComponentWise(`<=`, Vec) | |
# scalar 'approximately equal' function. | |
proc `~=`*(x, y: float32; tolerance = 1e-5): bool = | |
if x == y: | |
return true | |
let | |
ax = abs(x) | |
ay = abs(y) | |
diff = abs(x-y) | |
cmb = ax+ay | |
min = float32.minimumPositiveValue | |
max = float32.maximumPositiveValue | |
if x == 0 or y == 0 or (cmb < min): | |
return diff < (tolerance * min) | |
result = diff / min(cmb, max) < tolerance | |
# example of a component-wise 'approximately equal' function | |
genComponentWise(`~=`, Vec) | |
let x = [1f, 1f].Vec2 | |
let y = [1f, 2f].Vec2 | |
let z = [2f, 1f].Vec | |
echo x <= y | |
echo x <= z | |
echo y <= z | |
echo `<=`(y,z) | |
echo x ~= y | |
echo `~=`(x,y) | |
echo `~=`(x,y, 1e-5) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment