Last active
July 19, 2020 16:22
-
-
Save exelotl/1f387c47468d02be4587044ed270c907 to your computer and use it in GitHub Desktop.
Macro to expose `foo.x` as a shorthand for `foo.bar.x`
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 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