I'm looking for some advice on how to implement a proper conditional access operator.
The one in elvis: https://github.com/mattaylor/elvis/blob/master/elvis.nim is limited to using a proc for its right argument.
I want to be able to chain the ?. operator, e.g.: data?.obj.?prop
So I was looking into the experimental pattern operator *, https://nim-lang.github.io/Nim/manual_experimental.html#pattern-operators-the-nimstar-operator, but the argument types in my example could potentially be all different.
I was trying out something like:
proc `?.`(s: varargs[NimNode]): NimNode =
echo s.repr
template condAccess{ `?.` * a }(a: untyped): untyped = ?.a
type Prop = object
val*: int
type Data = object
prop*: ptr Prop
var data: ptr Data = cast[ptr Data](alloc(sizeof(Data)))
data?.prop
But I'm getting an error about undeclared identifier: 'prop'
You could write this as a macro, but it's also possible as a template and it's clearer this way. Template substitution works for field access, which makes this possible.
template `?.`(a, b): untyped =
let tmp = `a` # ensure `a` isn't evaluated twice
if tmp.isNil:
nil
else:
tmp.`b`
type Prop = object
val*: int
type Data = object
prop*: ptr Prop
var data: ptr Data = cast[ptr Data](alloc(sizeof(Data)))
data?.prop
Thanks for replying. I created a working PR before I saw your reply. I saw the issue with precedence as well and mattaylor the maintainer added .? as an alternative for ?. for higher precedence. I changed the implementation to untyped instead of using a proc type.
So this works:
template `.?`*(left, right: untyped): untyped =
if ?left: left.right
else:
var res: typeof(left.right)
res
So this works:
type
Data = ref object
val: int
Obj = ref object
data: Data
var nilObj:Obj
var objNilData = Obj()
var obj = Obj()
obj.data = Data()
obj.data.val = 10
...
suite "conditional access (chained)":
test "nil check": check(nilObj.?data == nil)
test "falsy on ref": check(nilObj.?data.?val == 0)
test "falsy on ref": check(objNilData.?data.?val == 0)
test "truthy on ref": check(obj.?data.?val == 10)
Thanks for the tip about wrapnils.
That's cool I didn't you know you could do this without genericizing it:
template `?.`(a: ref | ptr | pointer, b): untyped =
What's this called? The or'ing of types.Do you know how I could get this chaining to work with a proc that takes args?
https://play.nim-lang.org/#ix=3Md8
In the example, foo works, but bar doesn't.
I have no idea how to fix the issue with calling a proc with args using only templates.
I tried using the * operator: https://nim-lang.github.io/Nim/manual_experimental.html#pattern-operators-the-nimstar-operator like
import std / [macros]
{.hint[Pattern]:off.}
macro `.?`(args: varargs[untyped]): untyped =
echo args.repr
template condAcc{ `.?` * a }(a: untyped): untyped = `.?`(a)
type
Data = ref object
val: int
Obj = ref object
data: Data
var obj = Obj()
obj.data = Data()
obj.data.val = 10
proc foo(d: Data): int =
d.val
proc bar(d: Data, val: int): int =
d.val + val
obj.?data.?bar(1)
but condAcc doesn't match first. The .? macro matches and prints obj .? data, bar(1) instead of obj, data, bar(1).
I tried switching the .? macro to:
proc `.?`(args: varargs[NimNode]): NimNode =
echo args.repr
but I get an: Error: undeclared identifier 'data' since the pattern matches after semantic checking.
So I'm going to resort to a macro solution.
Thanks! The code referenced seems way shorter than what I came up with https://github.com/geekrelief/elvis/blob/8d4131477a8925a44e09affe381fc70d944cd5c3/elvis.nim#L49-L116
I'll have to look look more closely to see how I could simplify things. I attempted an anonymous proc approach earlier like what you have, but I wasn't able to hack it. That's probably the key to making things shorter.