#
#
# Nim's Runtime Library
# (c) Copyright 2015 Dominik Picheta
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements nice syntactic sugar based on Nim's
## macro system.
import std/private/since
import macros, typetraits
proc checkPragma(ex, prag: var NimNode) =
since (1, 3):
if ex.kind == nnkPragmaExpr:
prag = ex[1]
if ex[0].kind == nnkPar and ex[0].len == 1:
ex = ex[0][0]
else:
ex = ex[0]
proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
result = newNimNode(nnkProcTy)
var
formalParams = newNimNode(nnkFormalParams).add(b)
p = p
prag = newEmptyNode()
checkPragma(p, prag)
case p.kind
of nnkPar, nnkTupleConstr:
for i in 0 ..< p.len:
let ident = p[i]
var identDefs = newNimNode(nnkIdentDefs)
case ident.kind
of nnkExprColonExpr:
identDefs.add ident[0]
identDefs.add ident[1]
else:
identDefs.add newIdentNode("i" & $i)
identDefs.add(ident)
identDefs.add newEmptyNode()
formalParams.add identDefs
else:
var identDefs = newNimNode(nnkIdentDefs)
identDefs.add newIdentNode("i0")
identDefs.add(p)
identDefs.add newEmptyNode()
formalParams.add identDefs
result.add formalParams
result.add prag
macro `=>`*(p, b: untyped): untyped =
## Syntax sugar for anonymous procedures.
##
## .. code-block:: nim
##
## proc passTwoAndTwo(f: (int, int) -> int): int =
## f(2, 2)
##
## passTwoAndTwo((x, y) => x + y) # 4
var
params = @[ident"auto"]
name = newEmptyNode()
kind = nnkLambda
pragma = newEmptyNode()
p = p
checkPragma(p, pragma)
if p.kind == nnkInfix and p[0].kind == nnkIdent and p[0].eqIdent"->":
params[0] = p[2]
p = p[1]
checkPragma(p, pragma) # check again after -> transform
since (1, 3):
if p.kind == nnkCall:
# foo(x, y) => x + y
kind = nnkProcDef
name = p[0]
let newP = newNimNode(nnkPar)
for i in 1..
":
var procTy = createProcType(c[1], c[2])
params[0] = procTy[0][0]
for i in 1 ..< procTy[0].len:
params.add(procTy[0][i])
else:
error("Expected proc type (->) got (" & c[0].strVal & ").", c)
break
else:
error("Incorrect procedure parameter list.", c)
params.add(identDefs)
of nnkIdent:
var identDefs = newNimNode(nnkIdentDefs)
identDefs.add(p)
identDefs.add(ident"auto")
identDefs.add(newEmptyNode())
params.add(identDefs)
else:
error("Incorrect procedure parameter list.", p)
result = newProc(body = b, params = params,
pragmas = pragma, name = name,
procType = kind)
macro `->`*(p, b: untyped): untyped =
## Syntax sugar for procedure types.
##
## .. code-block:: nim
##
## proc pass2(f: (float, float) -> float): float =
## f(2, 2)
##
## # is the same as:
##
## proc pass2(f: proc (x, y: float): float): float =
## f(2, 2)
result = createProcType(p, b)
macro dump*(x: typed): untyped =
## Dumps the content of an expression, useful for debugging.
## It accepts any expression and prints a textual representation
## of the tree representing the expression - as it would appear in
## source code - together with the value of the expression.
##
## As an example,
##
## .. code-block:: nim
## let
## x = 10
## y = 20
## dump(x + y)
##
## will print ``x + y = 30``.
let s = x.toStrLit
let r = quote do:
debugEcho `s`, " = ", `x`
return r
# TODO: consider exporting this in macros.nim
proc freshIdentNodes(ast: NimNode): NimNode =
# Replace NimIdent and NimSym by a fresh ident node
# see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458
proc inspect(node: NimNode): NimNode =
case node.kind:
of nnkIdent, nnkSym:
result = ident($node)
of nnkEmpty, nnkLiterals:
result = node
else:
result = node.kind.newTree()
for child in node:
result.add inspect(child)
result = inspect(ast)
template distinctBase*(T: typedesc): typedesc {.deprecated: "use distinctBase from typetraits instead".} =
## reverses ``type T = distinct A``; works recursively.
typetraits.distinctBase(T)
macro capture*(locals: varargs[typed], body: untyped): untyped {.since: (1, 1).} =
## Useful when creating a closure in a loop to capture some local loop variables
## by their current iteration values. Example:
##
## .. code-block:: Nim
## import strformat, sequtils, sugar
## var myClosure : proc()
## for i in 5..7:
## for j in 7..9:
## if i * j == 42:
## capture i, j:
## myClosure = proc () = echo fmt"{i} * {j} = 42"
## myClosure() # output: 6 * 7 == 42
## let m = @[proc (s: string): string = "to " & s, proc (s: string): string = "not to " & s]
## var l = m.mapIt(capture(it, proc (s: string): string = it(s)))
## let r = l.mapIt(it("be"))
## echo r[0] & ", or " & r[1] # output: to be, or not to be
var params = @[newIdentNode("auto")]
let locals = if locals.len == 1 and locals[0].kind == nnkBracket: locals[0]
else: locals
for arg in locals:
params.add(newIdentDefs(ident(arg.strVal), freshIdentNodes getTypeInst arg))
result = newNimNode(nnkCall)
result.add(newProc(newEmptyNode(), params, body, nnkProcDef))
for arg in locals: result.add(arg)
since (1, 1):
import std / private / underscored_calls
macro dup*[T](arg: T, calls: varargs[untyped]): T =
## Turns an `in-place`:idx: algorithm into one that works on
## a copy and returns this copy.
## **Since**: Version 1.2.
runnableExamples:
import algorithm
var a = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
doAssert a.dup(sort) == sorted(a)
# Chaining:
var aCopy = a
aCopy.insert(10)
doAssert a.dup(insert(10), sort) == sorted(aCopy)
var s1 = "abc"
var s2 = "xyz"
doAssert s1 & s2 == s1.dup(&= s2)
result = newNimNode(nnkStmtListExpr, arg)
let tmp = genSym(nskVar, "dupResult")
result.add newVarStmt(tmp, arg)
underscoredCalls(result, calls, tmp)
result.add tmp
proc transLastStmt(n, res, bracketExpr: NimNode): (NimNode, NimNode, NimNode) {.since: (1, 1).} =
# Looks for the last statement of the last statement, etc...
case n.kind
of nnkIfExpr, nnkIfStmt, nnkTryStmt, nnkCaseStmt:
result[0] = copyNimTree(n)
result[1] = copyNimTree(n)
result[2] = copyNimTree(n)
for i in ord(n.kind == nnkCaseStmt)..= 1:
(result[0][^1], result[1][^1], result[2][^1]) = transLastStmt(n[^1], res, bracketExpr)
of nnkTableConstr:
result[1] = n[0][0]
result[2] = n[0][1]
if bracketExpr.len == 1:
bracketExpr.add([newCall(bindSym"typeof", newEmptyNode()), newCall(
bindSym"typeof", newEmptyNode())])
template adder(res, k, v) = res[k] = v
result[0] = getAst(adder(res, n[0][0], n[0][1]))
of nnkCurly:
result[2] = n[0]
if bracketExpr.len == 1:
bracketExpr.add(newCall(bindSym"typeof", newEmptyNode()))
template adder(res, v) = res.incl(v)
result[0] = getAst(adder(res, n[0]))
else:
result[2] = n
if bracketExpr.len == 1:
bracketExpr.add(newCall(bindSym"typeof", newEmptyNode()))
template adder(res, v) = res.add(v)
result[0] = getAst(adder(res, n))
macro collect*(init, body: untyped): untyped {.since: (1, 1).} =
## Comprehension for seq/set/table collections. ``init`` is
## the init call, and so custom collections are supported.
##
## The last statement of ``body`` has special syntax that specifies
## the collection's add operation. Use ``{e}`` for set's ``incl``,
## ``{k: v}`` for table's ``[]=`` and ``e`` for seq's ``add``.
##
## The ``init`` proc can be called with any number of arguments,
## i.e. ``initTable(initialSize)``.
runnableExamples:
import sets, tables
let data = @["bird", "word"]
## seq:
let k = collect(newSeq):
for i, d in data.pairs:
if i mod 2 == 0: d
assert k == @["bird"]
## seq with initialSize:
let x = collect(newSeqOfCap(4)):
for i, d in data.pairs:
if i mod 2 == 0: d
assert x == @["bird"]
## HashSet:
let y = initHashSet.collect:
for d in data.items: {d}
assert y == data.toHashSet
## Table:
let z = collect(initTable(2)):
for i, d in data.pairs: {i: d}
assert z == {0: "bird", 1: "word"}.toTable
# analyse the body, find the deepest expression 'it' and replace it via
# 'result.add it'
let res = genSym(nskVar, "collectResult")
expectKind init, {nnkCall, nnkIdent, nnkSym}
let bracketExpr = newTree(nnkBracketExpr,
if init.kind == nnkCall: init[0] else: init)
let (resBody, keyType, valueType) = transLastStmt(body, res, bracketExpr)
if bracketExpr.len == 3:
bracketExpr[1][1] = keyType
bracketExpr[2][1] = valueType
else:
bracketExpr[1][1] = valueType
let call = newTree(nnkCall, bracketExpr)
if init.kind == nnkCall:
for i in 1 ..< init.len:
call.add init[i]
result = newTree(nnkStmtListExpr, newVarStmt(res, call), resBody, res)
when isMainModule:
since (1, 1):
import algorithm
var a = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
doAssert dup(a, sort(_)) == sorted(a)
doAssert a.dup(sort) == sorted(a)
#Chaining:
var aCopy = a
aCopy.insert(10)
doAssert a.dup(insert(10)).dup(sort()) == sorted(aCopy)
import random
const b = @[0, 1, 2]
let c = b.dup shuffle()
doAssert c[0] == 1
doAssert c[1] == 0
#test collect
import sets, tables
let data = @["bird", "word"] # if this gets stuck in your head, its not my fault
assert collect(newSeq, for (i, d) in data.pairs: (if i mod 2 == 0: d)) == @["bird"]
assert collect(initTable(2), for (i, d) in data.pairs: {i: d}) == {0: "bird",
1: "word"}.toTable
assert initHashSet.collect(for d in data.items: {d}) == data.toHashSet
let x = collect(newSeqOfCap(4)):
for (i, d) in data.pairs:
if i mod 2 == 0: d
assert x == @["bird"]
# bug #12874
let bug1 = collect(
newSeq,
for (i, d) in data.pairs:(
block:
if i mod 2 == 0:
d
else:
d & d
)
)
assert bug1 == @["bird", "wordword"]
import strutils
let y = collect(newSeq):
for (i, d) in data.pairs:
try: parseInt(d) except: 0
assert y == @[0, 0]
let z = collect(newSeq):
for (i, d) in data.pairs:
case d
of "bird": "word"
else: d
assert z == @["word", "word"]