sugar.nim 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  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 macros
  12. proc createProcType(p, b: NimNode): NimNode {.compileTime.} =
  13. #echo treeRepr(p)
  14. #echo treeRepr(b)
  15. result = newNimNode(nnkProcTy)
  16. var formalParams = newNimNode(nnkFormalParams)
  17. formalParams.add b
  18. case p.kind
  19. of nnkPar, nnkTupleConstr:
  20. for i in 0 ..< p.len:
  21. let ident = p[i]
  22. var identDefs = newNimNode(nnkIdentDefs)
  23. case ident.kind
  24. of nnkExprColonExpr:
  25. identDefs.add ident[0]
  26. identDefs.add ident[1]
  27. else:
  28. identDefs.add newIdentNode("i" & $i)
  29. identDefs.add(ident)
  30. identDefs.add newEmptyNode()
  31. formalParams.add identDefs
  32. else:
  33. var identDefs = newNimNode(nnkIdentDefs)
  34. identDefs.add newIdentNode("i0")
  35. identDefs.add(p)
  36. identDefs.add newEmptyNode()
  37. formalParams.add identDefs
  38. result.add formalParams
  39. result.add newEmptyNode()
  40. #echo(treeRepr(result))
  41. #echo(result.toStrLit())
  42. macro `=>`*(p, b: untyped): untyped =
  43. ## Syntax sugar for anonymous procedures.
  44. ##
  45. ## .. code-block:: nim
  46. ##
  47. ## proc passTwoAndTwo(f: (int, int) -> int): int =
  48. ## f(2, 2)
  49. ##
  50. ## passTwoAndTwo((x, y) => x + y) # 4
  51. #echo treeRepr(p)
  52. #echo(treeRepr(b))
  53. var params: seq[NimNode] = @[newIdentNode("auto")]
  54. case p.kind
  55. of nnkPar, nnkTupleConstr:
  56. for c in children(p):
  57. var identDefs = newNimNode(nnkIdentDefs)
  58. case c.kind
  59. of nnkExprColonExpr:
  60. identDefs.add(c[0])
  61. identDefs.add(c[1])
  62. identDefs.add(newEmptyNode())
  63. of nnkIdent:
  64. identDefs.add(c)
  65. identDefs.add(newIdentNode("auto"))
  66. identDefs.add(newEmptyNode())
  67. of nnkInfix:
  68. if c[0].kind == nnkIdent and c[0].ident == !"->":
  69. var procTy = createProcType(c[1], c[2])
  70. params[0] = procTy[0][0]
  71. for i in 1 ..< procTy[0].len:
  72. params.add(procTy[0][i])
  73. else:
  74. error("Expected proc type (->) got (" & $c[0].ident & ").")
  75. break
  76. else:
  77. echo treeRepr c
  78. error("Incorrect procedure parameter list.")
  79. params.add(identDefs)
  80. of nnkIdent:
  81. var identDefs = newNimNode(nnkIdentDefs)
  82. identDefs.add(p)
  83. identDefs.add(newIdentNode("auto"))
  84. identDefs.add(newEmptyNode())
  85. params.add(identDefs)
  86. of nnkInfix:
  87. if p[0].kind == nnkIdent and p[0].ident == !"->":
  88. var procTy = createProcType(p[1], p[2])
  89. params[0] = procTy[0][0]
  90. for i in 1 ..< procTy[0].len:
  91. params.add(procTy[0][i])
  92. else:
  93. error("Expected proc type (->) got (" & $p[0].ident & ").")
  94. else:
  95. error("Incorrect procedure parameter list.")
  96. result = newProc(params = params, body = b, procType = nnkLambda)
  97. #echo(result.treeRepr)
  98. #echo(result.toStrLit())
  99. #return result # TODO: Bug?
  100. macro `->`*(p, b: untyped): untyped =
  101. ## Syntax sugar for procedure types.
  102. ##
  103. ## .. code-block:: nim
  104. ##
  105. ## proc pass2(f: (float, float) -> float): float =
  106. ## f(2, 2)
  107. ##
  108. ## # is the same as:
  109. ##
  110. ## proc pass2(f: proc (x, y: float): float): float =
  111. ## f(2, 2)
  112. result = createProcType(p, b)
  113. type ListComprehension = object
  114. var lc*: ListComprehension
  115. template `|`*(lc: ListComprehension, comp: untyped): untyped = lc
  116. macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped =
  117. ## List comprehension, returns a sequence. `comp` is the actual list
  118. ## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is
  119. ## the type that will be stored inside the result seq.
  120. ##
  121. ## .. code-block:: nim
  122. ##
  123. ## echo lc[x | (x <- 1..10, x mod 2 == 0), int]
  124. ##
  125. ## const n = 20
  126. ## echo lc[(x,y,z) | (x <- 1..n, y <- x..n, z <- y..n, x*x + y*y == z*z),
  127. ## tuple[a,b,c: int]]
  128. expectLen(comp, 3)
  129. expectKind(comp, nnkInfix)
  130. assert($comp[0] == "|")
  131. result = newCall(
  132. newDotExpr(
  133. newIdentNode("result"),
  134. newIdentNode("add")),
  135. comp[1])
  136. for i in countdown(comp[2].len-1, 0):
  137. let x = comp[2][i]
  138. expectMinLen(x, 1)
  139. if x[0].kind == nnkIdent and $x[0].ident == "<-":
  140. expectLen(x, 3)
  141. result = newNimNode(nnkForStmt).add(x[1], x[2], result)
  142. else:
  143. result = newIfStmt((x, result))
  144. result = newNimNode(nnkCall).add(
  145. newNimNode(nnkPar).add(
  146. newNimNode(nnkLambda).add(
  147. newEmptyNode(),
  148. newEmptyNode(),
  149. newEmptyNode(),
  150. newNimNode(nnkFormalParams).add(
  151. newNimNode(nnkBracketExpr).add(
  152. newIdentNode("seq"),
  153. typ)),
  154. newEmptyNode(),
  155. newEmptyNode(),
  156. newStmtList(
  157. newAssignment(
  158. newIdentNode("result"),
  159. newNimNode(nnkPrefix).add(
  160. newIdentNode("@"),
  161. newNimNode(nnkBracket))),
  162. result))))
  163. macro dump*(x: typed): untyped =
  164. ## Dumps the content of an expression, useful for debugging.
  165. ## It accepts any expression and prints a textual representation
  166. ## of the tree representing the expression - as it would appear in
  167. ## source code - together with the value of the expression.
  168. ##
  169. ## As an example,
  170. ##
  171. ## .. code-block:: nim
  172. ## let
  173. ## x = 10
  174. ## y = 20
  175. ## dump(x + y)
  176. ##
  177. ## will print ``x + y = 30``.
  178. let s = x.toStrLit
  179. let r = quote do:
  180. debugEcho `s`, " = ", `x`
  181. return r
  182. # TODO: consider exporting this in macros.nim
  183. proc freshIdentNodes(ast: NimNode): NimNode =
  184. # Replace NimIdent and NimSym by a fresh ident node
  185. # see also https://github.com/nim-lang/Nim/pull/8531#issuecomment-410436458
  186. proc inspect(node: NimNode): NimNode =
  187. case node.kind:
  188. of nnkIdent, nnkSym:
  189. result = ident($node)
  190. of nnkEmpty, nnkLiterals:
  191. result = node
  192. else:
  193. result = node.kind.newTree()
  194. for child in node:
  195. result.add inspect(child)
  196. result = inspect(ast)
  197. macro distinctBase*(T: typedesc): untyped =
  198. ## reverses ``type T = distinct A``; works recursively.
  199. runnableExamples:
  200. type T = distinct int
  201. doAssert distinctBase(T) is int
  202. doAssert: not compiles(distinctBase(int))
  203. type T2 = distinct T
  204. doAssert distinctBase(T2) is int
  205. let typeNode = getTypeImpl(T)
  206. expectKind(typeNode, nnkBracketExpr)
  207. if typeNode[0].typeKind != ntyTypeDesc:
  208. error "expected typeDesc, got " & $typeNode[0]
  209. var typeSym = typeNode[1]
  210. typeSym = getTypeImpl(typeSym)
  211. if typeSym.typeKind != ntyDistinct:
  212. error "type is not distinct"
  213. typeSym = typeSym[0]
  214. while typeSym.typeKind == ntyDistinct:
  215. typeSym = getTypeImpl(typeSym)[0]
  216. typeSym.freshIdentNodes