sugar.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2015 Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements nice syntactic sugar based on Nim's
  10. ## macro system.
  11. import std/private/since
  12. import macros
  13. proc checkPragma(ex, prag: var NimNode) =
  14. since (1, 3):
  15. if ex.kind == nnkPragmaExpr:
  16. prag = ex[1]
  17. ex = ex[0]
  18. proc createProcType(p, b: NimNode): NimNode =
  19. result = newNimNode(nnkProcTy)
  20. var
  21. formalParams = newNimNode(nnkFormalParams).add(b)
  22. p = p
  23. prag = newEmptyNode()
  24. checkPragma(p, prag)
  25. case p.kind
  26. of nnkPar, nnkTupleConstr:
  27. for i in 0 ..< p.len:
  28. let ident = p[i]
  29. var identDefs = newNimNode(nnkIdentDefs)
  30. case ident.kind
  31. of nnkExprColonExpr:
  32. identDefs.add ident[0]
  33. identDefs.add ident[1]
  34. else:
  35. identDefs.add newIdentNode("i" & $i)
  36. identDefs.add(ident)
  37. identDefs.add newEmptyNode()
  38. formalParams.add identDefs
  39. else:
  40. var identDefs = newNimNode(nnkIdentDefs)
  41. identDefs.add newIdentNode("i0")
  42. identDefs.add(p)
  43. identDefs.add newEmptyNode()
  44. formalParams.add identDefs
  45. result.add formalParams
  46. result.add prag
  47. macro `=>`*(p, b: untyped): untyped =
  48. ## Syntax sugar for anonymous procedures. It also supports pragmas.
  49. ##
  50. ## .. warning:: Semicolons can not be used to separate procedure arguments.
  51. runnableExamples:
  52. proc passTwoAndTwo(f: (int, int) -> int): int = f(2, 2)
  53. assert passTwoAndTwo((x, y) => x + y) == 4
  54. type
  55. Bot = object
  56. call: (string {.noSideEffect.} -> string)
  57. var myBot = Bot()
  58. myBot.call = (name: string) {.noSideEffect.} => "Hello " & name & ", I'm a bot."
  59. assert myBot.call("John") == "Hello John, I'm a bot."
  60. let f = () => (discard) # simplest proc that returns void
  61. f()
  62. var
  63. params = @[ident"auto"]
  64. name = newEmptyNode()
  65. kind = nnkLambda
  66. pragma = newEmptyNode()
  67. p = p
  68. checkPragma(p, pragma)
  69. if p.kind == nnkInfix and p[0].kind == nnkIdent and p[0].eqIdent"->":
  70. params[0] = p[2]
  71. p = p[1]
  72. checkPragma(p, pragma) # check again after -> transform
  73. case p.kind
  74. of nnkPar, nnkTupleConstr:
  75. var untypedBeforeColon = 0
  76. for i, c in p:
  77. var identDefs = newNimNode(nnkIdentDefs)
  78. case c.kind
  79. of nnkExprColonExpr:
  80. let t = c[1]
  81. since (1, 3):
  82. # + 1 here because of return type in params
  83. for j in (i - untypedBeforeColon + 1) .. i:
  84. params[j][1] = t
  85. untypedBeforeColon = 0
  86. identDefs.add(c[0])
  87. identDefs.add(t)
  88. identDefs.add(newEmptyNode())
  89. of nnkIdent:
  90. identDefs.add(c)
  91. identDefs.add(newIdentNode("auto"))
  92. identDefs.add(newEmptyNode())
  93. inc untypedBeforeColon
  94. of nnkInfix:
  95. if c[0].kind == nnkIdent and c[0].eqIdent"->":
  96. var procTy = createProcType(c[1], c[2])
  97. params[0] = procTy[0][0]
  98. for i in 1 ..< procTy[0].len:
  99. params.add(procTy[0][i])
  100. else:
  101. error("Expected proc type (->) got (" & c[0].strVal & ").", c)
  102. break
  103. else:
  104. error("Incorrect procedure parameter.", c)
  105. params.add(identDefs)
  106. of nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice, nnkSym:
  107. var identDefs = newNimNode(nnkIdentDefs)
  108. identDefs.add(ident $p)
  109. identDefs.add(ident"auto")
  110. identDefs.add(newEmptyNode())
  111. params.add(identDefs)
  112. else:
  113. error("Incorrect procedure parameter list.", p)
  114. result = newProc(body = b, params = params,
  115. pragmas = pragma, name = name,
  116. procType = kind)
  117. macro `->`*(p, b: untyped): untyped =
  118. ## Syntax sugar for procedure types. It also supports pragmas.
  119. ##
  120. ## .. warning:: Semicolons can not be used to separate procedure arguments.
  121. runnableExamples:
  122. proc passTwoAndTwo(f: (int, int) -> int): int = f(2, 2)
  123. # is the same as:
  124. # proc passTwoAndTwo(f: proc (x, y: int): int): int = f(2, 2)
  125. assert passTwoAndTwo((x, y) => x + y) == 4
  126. proc passOne(f: (int {.noSideEffect.} -> int)): int = f(1)
  127. # is the same as:
  128. # proc passOne(f: proc (x: int): int {.noSideEffect.}): int = f(1)
  129. assert passOne(x {.noSideEffect.} => x + 1) == 2
  130. result = createProcType(p, b)
  131. macro dump*(x: untyped): untyped =
  132. ## Dumps the content of an expression, useful for debugging.
  133. ## It accepts any expression and prints a textual representation
  134. ## of the tree representing the expression - as it would appear in
  135. ## source code - together with the value of the expression.
  136. ##
  137. ## See also: `dumpToString` which is more convenient and useful since
  138. ## it expands intermediate templates/macros, returns a string instead of
  139. ## calling `echo`, and works with statements and expressions.
  140. runnableExamples("-r:off"):
  141. let
  142. x = 10
  143. y = 20
  144. dump(x + y) # prints: `x + y = 30`
  145. let s = x.toStrLit
  146. result = quote do:
  147. debugEcho `s`, " = ", `x`
  148. macro dumpToStringImpl(s: static string, x: typed): string =
  149. let s2 = x.toStrLit
  150. if x.typeKind == ntyVoid:
  151. result = quote do:
  152. `s` & ": " & `s2`
  153. else:
  154. result = quote do:
  155. `s` & ": " & `s2` & " = " & $`x`
  156. macro dumpToString*(x: untyped): string =
  157. ## Returns the content of a statement or expression `x` after semantic analysis,
  158. ## useful for debugging.
  159. runnableExamples:
  160. const a = 1
  161. let x = 10
  162. assert dumpToString(a + 2) == "a + 2: 3 = 3"
  163. assert dumpToString(a + x) == "a + x: 1 + x = 11"
  164. template square(x): untyped = x * x
  165. assert dumpToString(square(x)) == "square(x): x * x = 100"
  166. assert not compiles dumpToString(1 + nonexistent)
  167. import std/strutils
  168. assert "failedAssertImpl" in dumpToString(assert true) # example with a statement
  169. result = newCall(bindSym"dumpToStringImpl")
  170. result.add newLit repr(x)
  171. result.add x
  172. # TODO: consider exporting this in macros.nim
  173. proc freshIdentNodes(ast: NimNode): NimNode =
  174. # Replace NimIdent and NimSym by a fresh ident node
  175. # see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458
  176. proc inspect(node: NimNode): NimNode =
  177. case node.kind:
  178. of nnkIdent, nnkSym:
  179. result = ident($node)
  180. of nnkEmpty, nnkLiterals:
  181. result = node
  182. else:
  183. result = node.kind.newTree()
  184. for child in node:
  185. result.add inspect(child)
  186. result = inspect(ast)
  187. macro capture*(locals: varargs[typed], body: untyped): untyped {.since: (1, 1).} =
  188. ## Useful when creating a closure in a loop to capture some local loop variables
  189. ## by their current iteration values.
  190. runnableExamples:
  191. import std/strformat
  192. var myClosure: () -> string
  193. for i in 5..7:
  194. for j in 7..9:
  195. if i * j == 42:
  196. capture i, j:
  197. myClosure = () => fmt"{i} * {j} = 42"
  198. assert myClosure() == "6 * 7 = 42"
  199. var params = @[newIdentNode("auto")]
  200. let locals = if locals.len == 1 and locals[0].kind == nnkBracket: locals[0]
  201. else: locals
  202. for arg in locals:
  203. proc getIdent(n: NimNode): NimNode =
  204. case n.kind
  205. of nnkIdent, nnkSym:
  206. let nStr = n.strVal
  207. if nStr == "result":
  208. error("The variable name cannot be `result`!", n)
  209. result = ident(nStr)
  210. of nnkHiddenDeref: result = n[0].getIdent()
  211. else:
  212. error("The argument to be captured `" & n.repr & "` is not a pure identifier. " &
  213. "It is an unsupported `" & $n.kind & "` node.", n)
  214. let argName = getIdent(arg)
  215. params.add(newIdentDefs(argName, freshIdentNodes getTypeInst arg))
  216. result = newNimNode(nnkCall)
  217. result.add(newProc(newEmptyNode(), params, body, nnkLambda))
  218. for arg in locals: result.add(arg)
  219. since (1, 1):
  220. import std/private/underscored_calls
  221. macro dup*[T](arg: T, calls: varargs[untyped]): T =
  222. ## Turns an `in-place`:idx: algorithm into one that works on
  223. ## a copy and returns this copy, without modifying its input.
  224. ##
  225. ## This macro also allows for (otherwise in-place) function chaining.
  226. ##
  227. ## **Since:** Version 1.2.
  228. runnableExamples:
  229. import std/algorithm
  230. let a = @[1, 2, 3, 4, 5, 6, 7, 8, 9]
  231. assert a.dup(sort) == sorted(a)
  232. # Chaining:
  233. var aCopy = a
  234. aCopy.insert(10)
  235. assert a.dup(insert(10), sort) == sorted(aCopy)
  236. let s1 = "abc"
  237. let s2 = "xyz"
  238. assert s1 & s2 == s1.dup(&= s2)
  239. # An underscore (_) can be used to denote the place of the argument you're passing:
  240. assert "".dup(addQuoted(_, "foo")) == "\"foo\""
  241. # but `_` is optional here since the substitution is in 1st position:
  242. assert "".dup(addQuoted("foo")) == "\"foo\""
  243. proc makePalindrome(s: var string) =
  244. for i in countdown(s.len-2, 0):
  245. s.add(s[i])
  246. let c = "xyz"
  247. # chaining:
  248. let d = dup c:
  249. makePalindrome # xyzyx
  250. sort(_, SortOrder.Descending) # zyyxx
  251. makePalindrome # zyyxxxyyz
  252. assert d == "zyyxxxyyz"
  253. result = newNimNode(nnkStmtListExpr, arg)
  254. let tmp = genSym(nskVar, "dupResult")
  255. result.add newVarStmt(tmp, arg)
  256. underscoredCalls(result, calls, tmp)
  257. result.add tmp
  258. proc trans(n, res, bracketExpr: NimNode): (NimNode, NimNode, NimNode) {.since: (1, 1).} =
  259. # Looks for the last statement of the last statement, etc...
  260. case n.kind
  261. of nnkIfExpr, nnkIfStmt, nnkTryStmt, nnkCaseStmt, nnkWhenStmt:
  262. result[0] = copyNimTree(n)
  263. result[1] = copyNimTree(n)
  264. result[2] = copyNimTree(n)
  265. for i in ord(n.kind == nnkCaseStmt) ..< n.len:
  266. (result[0][i], result[1][^1], result[2][^1]) = trans(n[i], res, bracketExpr)
  267. of nnkStmtList, nnkStmtListExpr, nnkBlockStmt, nnkBlockExpr, nnkWhileStmt,
  268. nnkForStmt, nnkElifBranch, nnkElse, nnkElifExpr, nnkOfBranch, nnkExceptBranch:
  269. result[0] = copyNimTree(n)
  270. result[1] = copyNimTree(n)
  271. result[2] = copyNimTree(n)
  272. if n.len >= 1:
  273. (result[0][^1], result[1][^1], result[2][^1]) = trans(n[^1],
  274. res, bracketExpr)
  275. of nnkTableConstr:
  276. result[1] = n[0][0]
  277. result[2] = n[0][1]
  278. if bracketExpr.len == 0:
  279. bracketExpr.add(ident"initTable") # don't import tables
  280. if bracketExpr.len == 1:
  281. bracketExpr.add([newCall(bindSym"typeof",
  282. newEmptyNode()), newCall(bindSym"typeof", newEmptyNode())])
  283. template adder(res, k, v) = res[k] = v
  284. result[0] = getAst(adder(res, n[0][0], n[0][1]))
  285. of nnkCurly:
  286. result[2] = n[0]
  287. if bracketExpr.len == 0:
  288. bracketExpr.add(ident"initHashSet")
  289. if bracketExpr.len == 1:
  290. bracketExpr.add(newCall(bindSym"typeof", newEmptyNode()))
  291. template adder(res, v) = res.incl(v)
  292. result[0] = getAst(adder(res, n[0]))
  293. else:
  294. result[2] = n
  295. if bracketExpr.len == 0:
  296. bracketExpr.add(bindSym"newSeq")
  297. if bracketExpr.len == 1:
  298. bracketExpr.add(newCall(bindSym"typeof", newEmptyNode()))
  299. template adder(res, v) = res.add(v)
  300. result[0] = getAst(adder(res, n))
  301. proc collectImpl(init, body: NimNode): NimNode {.since: (1, 1).} =
  302. let res = genSym(nskVar, "collectResult")
  303. var bracketExpr: NimNode
  304. if init != nil:
  305. expectKind init, {nnkCall, nnkIdent, nnkSym}
  306. bracketExpr = newTree(nnkBracketExpr,
  307. if init.kind == nnkCall: freshIdentNodes(init[0]) else: freshIdentNodes(init))
  308. else:
  309. bracketExpr = newTree(nnkBracketExpr)
  310. let (resBody, keyType, valueType) = trans(body, res, bracketExpr)
  311. if bracketExpr.len == 3:
  312. bracketExpr[1][1] = keyType
  313. bracketExpr[2][1] = valueType
  314. else:
  315. bracketExpr[1][1] = valueType
  316. let call = newTree(nnkCall, bracketExpr)
  317. if init != nil and init.kind == nnkCall:
  318. for i in 1 ..< init.len:
  319. call.add init[i]
  320. result = newTree(nnkStmtListExpr, newVarStmt(res, call), resBody, res)
  321. macro collect*(init, body: untyped): untyped {.since: (1, 1).} =
  322. ## Comprehension for seqs/sets/tables.
  323. ##
  324. ## The last expression of `body` has special syntax that specifies
  325. ## the collection's add operation. Use `{e}` for set's `incl`,
  326. ## `{k: v}` for table's `[]=` and `e` for seq's `add`.
  327. # analyse the body, find the deepest expression 'it' and replace it via
  328. # 'result.add it'
  329. runnableExamples:
  330. import std/[sets, tables]
  331. let data = @["bird", "word"]
  332. ## seq:
  333. let k = collect(newSeq):
  334. for i, d in data.pairs:
  335. if i mod 2 == 0: d
  336. assert k == @["bird"]
  337. ## seq with initialSize:
  338. let x = collect(newSeqOfCap(4)):
  339. for i, d in data.pairs:
  340. if i mod 2 == 0: d
  341. assert x == @["bird"]
  342. ## HashSet:
  343. let y = collect(initHashSet()):
  344. for d in data.items: {d}
  345. assert y == data.toHashSet
  346. ## Table:
  347. let z = collect(initTable(2)):
  348. for i, d in data.pairs: {i: d}
  349. assert z == {0: "bird", 1: "word"}.toTable
  350. result = collectImpl(init, body)
  351. macro collect*(body: untyped): untyped {.since: (1, 5).} =
  352. ## Same as `collect` but without an `init` parameter.
  353. ##
  354. ## **See also:**
  355. ## * `sequtils.toSeq proc<sequtils.html#toSeq.t%2Cuntyped>`_
  356. ## * `sequtils.mapIt template<sequtils.html#mapIt.t%2Ctyped%2Cuntyped>`_
  357. runnableExamples:
  358. import std/[sets, tables]
  359. let data = @["bird", "word"]
  360. # seq:
  361. let k = collect:
  362. for i, d in data.pairs:
  363. if i mod 2 == 0: d
  364. assert k == @["bird"]
  365. ## HashSet:
  366. let n = collect:
  367. for d in data.items: {d}
  368. assert n == data.toHashSet
  369. ## Table:
  370. let m = collect:
  371. for i, d in data.pairs: {i: d}
  372. assert m == {0: "bird", 1: "word"}.toTable
  373. result = collectImpl(nil, body)