Skip to content

Instantly share code, notes, and snippets.

@exelotl
Last active July 19, 2020 16:22
Show Gist options
  • Save exelotl/1f387c47468d02be4587044ed270c907 to your computer and use it in GitHub Desktop.
Save exelotl/1f387c47468d02be4587044ed270c907 to your computer and use it in GitHub Desktop.
Macro to expose `foo.x` as a shorthand for `foo.bar.x`
import macros
macro compose*(t:typedesc, field:untyped) =
##
## Composition helper to expose subfields as if they belonged to the root type.
##
## E.g. allow `foo.x` as a shorthand for `foo.bar.x`
##
## **Parameters:**
##
## t
## An object type for which new getters/setters will be created.
##
## field
## The name of a field within `t`. This field should be of some object type.
##
##
## **Example:**
##
## .. code-block:: nim
## type
## Vec2 = object
## x, y: float
## Player = object
## position: Vec2
##
## compose Player, position # define getters/setters for `x` and `y` on type `Player`
##
## var p: Player
## p.y += 20 # shorthand for `p.position.y += 20`
## p.x = p.y + 10 # shorthand for `p.position.x = p.position.y + 10`
##
iterator fields(ty:NimNode): tuple[name, ty: NimNode, exported: bool] =
## Iterate over all fields of a given object type
var objTy = getImpl(ty)[2]
# Unwrap ref/ptr types
if objTy.kind in {nnkRefTy, nnkPtrTy}:
objTy = objTy[0]
# Check that the type is actually an object
expectKind(objTy, nnkObjectTy)
for f in objTy[2]:
let fty = f[f.len-2]
for i in 0..<(f.len-2):
case f[i].kind
of nnkIdent: yield (f[i], fty, false)
of nnkPostfix: yield (f[i][1], fty, true)
else: error("not a field name??", f[i])
# Find the type of `field`
var fieldTy: NimNode
for name, ty, _ in fields(t):
if eqIdent(name, field):
fieldTy = ty
result = newStmtList()
let self = ident "self"
let val = ident "val"
# Generate getters and setters for all the members of `field`
for name, ty, exported in fields(fieldTy):
var getter = name
var setter = nnkAccQuoted.newTree(name, ident "=")
if exported:
getter = postfix(getter, "*")
setter = postfix(setter, "*")
result.add quote do:
template `getter`(`self`: `t`): `ty` {.used.} =
`self`.`field`.`name`
template `getter`(`self`: var `t`): var `ty` {.used.} =
`self`.`field`.`name`
template `setter`(`self`: var `t`, `val`:`ty`) {.used.} =
`self`.`field`.`name` = `val`
echo repr result
# Example:
when isMainModule:
type
Vec2 = object
x, y: float
Player = object
position: Vec2
compose Player, position
var p: Player
p.y += 20
p.x = p.y + 10
echo p.x # 30
echo p.y # 20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment