Skip to content

Instantly share code, notes, and snippets.

@rayman22201
Last active March 19, 2020 06:16
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 rayman22201/bfc4ce11fa15e57176130c4855407199 to your computer and use it in GitHub Desktop.
Save rayman22201/bfc4ce11fa15e57176130c4855407199 to your computer and use it in GitHub Desktop.
lift boolean procs from basic types to component wise containers
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