Skip to content
Closed
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

- Added `os.isRelativeTo` to tell whether a path is relative to another

- Added `sugar.byAddr` allowing a reference syntax for lvalue expressions, analog to C++ `auto& a = expr`.

## Library changes

- `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations`
Expand Down
36 changes: 36 additions & 0 deletions lib/pure/sugar.nim
Original file line number Diff line number Diff line change
Expand Up @@ -364,3 +364,39 @@ when (NimMajor, NimMinor) >= (1, 1):
of "bird": "word"
else: d
assert z == @["word", "word"]

proc splitDefinition*(def: NimNode): tuple[lhs: NimNode, rhs: NimNode, op: string] {.since: (1,1).} =
## allows library constructs such as:
## `myDef: a2=expr`
## `myDef: a2*=expr`
def.expectKind nnkStmtList
def.expectLen 1
let def2 = def[0]
case def2.kind
of nnkInfix:
result.lhs = def2[1]
result.rhs = def2[2]
result.op = def2[0].strVal
of nnkAsgn:
result.lhs = def2[0]
result.rhs = def2[1]
result.op = "="
else: doAssert false, $def2.kind
expectKind(result.lhs, nnkIdent)

macro byAddr*(def: untyped): untyped {.since: (1,1).} =
## Defines a reference syntax for lvalue expressions, analog to C++ `auto& a = expr`.
## The expression is evaluated only once, and any side effects will only be
## evaluated once, at declaration time.
runnableExamples:
var x = @[1,2,3]
byAddr: x1=x[1]
x1+=10
doAssert type(x1) is int and x == @[1,12,3]

let (name, exp, op) = splitDefinition(def)
if op != "=":
error("expected `=`, got `" & op & "`", def)
result = quote do:
let myAddr = addr `exp`
template `name`: untyped = myAddr[]
10 changes: 10 additions & 0 deletions tests/stdlib/msugar.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import std/sugar

type Bar = object
x: int
type Foo = object
bar: Bar

var foo*: Foo
byAddr: barx = foo.bar.x
export barx
17 changes: 0 additions & 17 deletions tests/stdlib/tstring.nim
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
discard """
output: '''OK
@[@[], @[], @[], @[], @[]]
'''
"""
const characters = "abcdefghijklmnopqrstuvwxyz"
const numbers = "1234567890"

Expand Down Expand Up @@ -79,15 +74,3 @@ proc test_string_cmp() =
test_string_slice()
test_string_cmp()


#--------------------------
# bug #7816
import sugar
import sequtils

proc tester[T](x: T) =
let test = toSeq(0..4).map(i => newSeq[int]())
echo test

tester(1)

39 changes: 39 additions & 0 deletions tests/stdlib/tsugar.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import sugar
import macros

block byAddrBlock:
var count = 0
proc identity(a: int): auto =
block: count.inc; a
var x = @[1,2,3]
byAddr: x1=x[identity(1)] # the lvalue expression is evaluated only here
doAssert count == 1
x1 += 10
doAssert type(x1) is int # use x1 just like a normal variable
doAssert x == @[1,12,3]
doAssert count == 1 # count has not changed
doAssert compiles (block: byAddr: x2=x[0])
doAssert not compiles (block: byAddr: x2=y[0])
# correctly does not compile when using invalid lvalue expression

block byPtrfBlock:
type Foo = object
x: string
proc fun(a: Foo): auto =
doAssert not compiles (block: byAddr: x=a.x)
let foo = Foo(x: "asdf")
fun(foo)

import ./msugar
# test byAddr with export
barx += 10
doAssert $foo == "(bar: (x: 10))"

# bug #7816
import sequtils

proc tester[T](x: T) =
let test = toSeq(0..4).map(i => newSeq[int]())
doAssert $test == """@[@[], @[], @[], @[], @[]]"""

tester(1)