Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std/views (1st class openArrray, but not escape-safe); std/pointers; fixes sequtils.applyIt #14869

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 39 additions & 18 deletions lib/pure/collections/sequtils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -964,26 +964,47 @@ template mapIt*(s: typed, op: untyped): untyped =
op
map(s, f)

template applyIt*(varSeq, op: untyped) =
## Convenience template around the mutable ``apply`` proc to reduce typing.
##
## The template injects the ``it`` variable which you can use directly in an
## expression. The expression has to return the same type as the sequence you
## are mutating.
##
## See also:
## * `apply proc<#apply,openArray[T],proc(T)_2>`_
## * `mapIt template<#mapIt.t,typed,untyped>`_
##
runnableExamples:
var nums = @[1, 2, 3, 4]
nums.applyIt(it * 3)
assert nums[0] + nums[3] == 15
import std/views

for i in low(varSeq) .. high(varSeq):
let it {.inject.} = varSeq[i]
varSeq[i] = op
template evalonceVar(lhs, typ, expr) =
## makes sure `expr` is evaluated once, and no copy is done when using
## lvalues. The only current limitation is when expr is an expression returning
## an openArray and is not a symbol.
runnableExamples:
let s = @[1,2]
let s2 {.evalonce.} = s
doAssert s2[0].unsafeAddr == s[0].unsafeAddr
# this should eventually be moved to std/decls

# when type(expr) is openArray: # hits https://github.com/nim-lang/Nim/issues/12030
type Expr = type(expr)
when Expr is openArray:
static: doAssert typ is type(nil) # we could support but a bit pointless
# caveat: that's the only case that's not sideeffect safe;
# it could be made safe either with 1st class openArray, or with a
# macro that transforms `expr` aka `(body; last)` into:
# `body; let tmp = unsafeAddr(last)`
# template lhs: untyped = expr
let lhs = mview(expr)
else:
when typ is type(nil):
let tmp = addr(expr)
else:
let tmp: ptr typ = addr(expr)
template lhs: untyped = tmp[]

template applyIt*(varSeq, op: untyped) =
#[
A better syntax would be: `var s {.evalonce.} = varSeq` but this
is blocked by 2 issues:
https://github.com/nim-lang/RFCs/issues/220 to allow disinguishing
var vs let (vs const)
and https://github.com/timotheecour/Nim/issues/89, which allows
using pragma macro inside a template
]#
evalonceVar(s, nil, varSeq)
for it {.inject.} in mitems(s):
it = op

template newSeqWith*(len: int, init: untyped): untyped =
## Creates a new sequence of length `len`, calling `init` to initialize
Expand Down
20 changes: 20 additions & 0 deletions lib/std/pointers.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
template `+`*[T](p: ptr T, off: int): ptr T =
type T = typeof(p[]) # pending https://github.com/nim-lang/Nim/issues/13527
cast[ptr T](cast[ByteAddress](p) +% off * sizeof(T))

template `-`*[T](p: ptr T, off: int): ptr T =
type T = typeof(p[])
cast[ptr T](cast[ByteAddress](p) -% off * sizeof(T))

template `[]`*[T](p: ptr T, off: int): T =
(p + off)[]

template `[]=`*[T](p: ptr T, off: int, val: T) =
(p + off)[] = val

proc `+=`*[T](p: ptr T, off: int) {.inline.} =
# not a template to avoid double evaluation issues
p = p + off

proc `-=`*[T](p: ptr T, off: int) {.inline.} =
p = p - off
66 changes: 66 additions & 0 deletions lib/std/views.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
##[
Aka in other languages as: slice (in go), span (in C#).
Note that `system.Slice` already exists with a different meaning.

Note: experimental module, unstable API.
]##

import std/pointers

type View*[T] = object
## provides a view over a region of memory representing elements of type T
data*: ptr T
len*: int # TODO: or lenImpl + template accessor?

type MView*[T] = object
data*: ptr T
len*: int

type SomeView*[T] = View[T]|MView[T]

proc view*[T](a: openArray[T]): View[T] {.inline.} =
## return an immutable view over `a`
# PRTEMP: unsafeAddr?
result = View[T](data: a[0].addr, len: a.len)

proc view*[T](a: var openArray[T]): View[T] {.inline.} =
## return an immutable view over `a`
result = View[T](data: a[0].addr, len: a.len)

# proc toView*[T](a: var openArray[T]): var View[T] =
# let x = a[0].unsafeAddr
# # result = View[T](data: a[0].unsafeAddr, len: a.len)
# result = View[T](data: x, len: a.len)

proc mview*[T](a: var openArray[T]): MView[T] {.inline.} =
## return a mutable view over `a`
result = MView[T](data: a[0].addr, len: a.len)

# iterator items*[T](a: MView[T] | View[T]): lent T =
iterator items*[T](a: SomeView[T]): lent T =
## iterator over `a`
for i in 0..<a.len:
yield a.data[i]

iterator mitems*[T](a: MView[T]): var T =
## mutable iterator over `a`
for i in 0..<a.len:
yield a.data[i]

proc `[]`*[T, I](a: View[T], r: Slice[I]): View[T] {.inline.} =
assert r.a >= 0
assert r.b < a.len
result = View[T](data: a.data + r.a, len: r.len)

proc `[]`*[T](a: View[T], index: int): lent T {.inline.} =
a.data[index]

proc `[]`*[T](a: MView[T], index: int): var T {.inline.} =
# TODO: `a: var MView[T]`?
a.data[index]

# proc `[]`*(a: View[T], index: int): lent T =
# a.data[index]

proc `[]=`*[T](a: var MView[T], index: int, b: T) {.inline.} =
a.data[index] = b
2 changes: 1 addition & 1 deletion testament/important_packages.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pkg1 "elvis"
pkg1 "fidget", true
pkg1 "fragments", false, "nim c -r fragments/dsl.nim"
pkg1 "gara"
pkg1 "ggplotnim", true, "nim c -d:noCairo -r tests/tests.nim"
# pkg1 "ggplotnim", true, "nim c -d:noCairo -r tests/tests.nim" # pending https://github.com/Vindaar/ginger/pull/22
# pkg1 "gittyup", true, "nimble test", "https://github.com/disruptek/gittyup"
pkg1 "glob"
pkg1 "gnuplot"
Expand Down
53 changes: 49 additions & 4 deletions tests/stdlib/tsequtils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,60 @@ block: # foldr tests
assert concatenation == "nimiscool"
doAssert toSeq(1..3).foldr(a + b) == 6 # issue #14404

block: # mapIt + applyIt test
block: # mapIt tests
counter = 0
var
nums = @[1, 2, 3, 4]
strings = nums.identity.mapIt($(4 * it))
doAssert counter == 1
nums.applyIt(it * 3)
assert nums[0] + nums[3] == 15
assert strings[2] == "12"
doAssert strings == @["4", "8", "12", "16"]

block: # applyIt tests
const expected = @[3, 6, 9]
block:
var a = @[1, 2, 3]
a.applyIt(it * 3)
doAssert a == expected

block:
var a = @[1,2,3]
applyIt a, it*3
doAssert a == expected

block:
var count = 0
applyIt (var a = @[1,2,3]; count.inc; a), it*3
doAssert count == 1
doAssert a == expected

block:
proc fn(a: var openArray[int]) =
applyIt(a, it*3)
var a = @[1,2,3]
fn(a)
doAssert a == expected

block:
var count = 0
proc fn(a: var openArray[int]) =
applyIt((count.inc; a), it*3)
var a = @[1,2,3]
fn(a)
doAssert a == expected
doAssert count == 1

block:
template bar() =
let a = @[1,2,3]
applyIt a, it*3
doAssert a == expected
doAssert not compiles(bar()) # because of `let`

block:
template bar() =
proc fn(a: openArray[int]) =
applyIt(a, it*3)
doAssert not compiles(bar()) # because of `let` param

block: # newSeqWith tests
var seq2D = newSeqWith(4, newSeq[bool](2))
Expand Down
16 changes: 16 additions & 0 deletions tests/stdlib/tviews.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import std/views
from std/sequtils import toSeq

block:
var a = @[1,2,3]
let v = a.view()
doAssert toSeq(v) == a
doAssert v[1] == 2

block:
var a = @[1,2,3,4]
# let v = a.view()[1..^1] # TODO
let v = a.view()[1..a.len-2]
doAssert toSeq(v) == @[2,3]
doAssert v[0] == 2
# doAssert v[^1] == 3 # TODO