dochelpers.nim 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2021 Nim contributors
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Integration helpers between ``docgen.nim`` and ``rst.nim``.
  10. ##
  11. ## Function `toLangSymbol(linkText)`_ produces a signature `docLink` of
  12. ## `type LangSymbol`_ in ``rst.nim``, while `match(generated, docLink)`_
  13. ## matches it with `generated`, produced from `PNode` by ``docgen.rst``.
  14. import rstast
  15. when defined(nimPreviewSlimSystem):
  16. import std/[assertions, syncio]
  17. type
  18. LangSymbol* = object ## symbol signature in Nim
  19. symKind*: string ## "proc", "const", "type", etc
  20. symTypeKind*: string ## ""|enum|object|tuple -
  21. ## valid only when `symKind == "type"`
  22. name*: string ## plain symbol name without any parameters
  23. generics*: string ## generic parameters (without brackets)
  24. isGroup*: bool ## is LangSymbol a group with overloads?
  25. # the following fields are valid iff `isGroup` == false
  26. # (always false when parsed by `toLangSymbol` because link like foo_
  27. # can point to just a single symbol foo, e.g. proc).
  28. parametersProvided*: bool ## to disambiguate `proc f`_ and `proc f()`_
  29. parameters*: seq[tuple[name: string, `type`: string]]
  30. ## name-type seq, e.g. for proc
  31. outType*: string ## result type, e.g. for proc
  32. func nimIdentBackticksNormalize*(s: string): string =
  33. ## Normalizes the string `s` as a Nim identifier.
  34. ##
  35. ## Unlike `nimIdentNormalize` removes spaces and backticks.
  36. ##
  37. ## .. Warning:: No checking (e.g. that identifiers cannot start from
  38. ## digits or '_', or that number of backticks is even) is performed.
  39. runnableExamples:
  40. doAssert nimIdentBackticksNormalize("Foo_bar") == "Foobar"
  41. doAssert nimIdentBackticksNormalize("FoO BAr") == "Foobar"
  42. doAssert nimIdentBackticksNormalize("`Foo BAR`") == "Foobar"
  43. doAssert nimIdentBackticksNormalize("` Foo BAR `") == "Foobar"
  44. # not a valid identifier:
  45. doAssert nimIdentBackticksNormalize("`_x_y`") == "_xy"
  46. result = newString(s.len)
  47. var firstChar = true
  48. var j = 0
  49. for i in 0..len(s) - 1:
  50. if s[i] in {'A'..'Z'}:
  51. if not firstChar: # to lowercase
  52. result[j] = chr(ord(s[i]) + (ord('a') - ord('A')))
  53. else:
  54. result[j] = s[i]
  55. firstChar = false
  56. inc j
  57. elif s[i] notin {'_', ' ', '`'}:
  58. result[j] = s[i]
  59. inc j
  60. firstChar = false
  61. elif s[i] == '_' and firstChar:
  62. result[j] = '_'
  63. inc j
  64. firstChar = false
  65. else: discard # just omit '`' or ' '
  66. if j != s.len: setLen(result, j)
  67. proc toLangSymbol*(linkText: PRstNode): LangSymbol =
  68. ## Parses `linkText` into a more structured form using a state machine.
  69. ##
  70. ## This proc is designed to allow link syntax with operators even
  71. ## without escaped backticks inside:
  72. ##
  73. ## `proc *`_
  74. ## `proc []`_
  75. ##
  76. ## This proc should be kept in sync with the `renderTypes` proc from
  77. ## ``compiler/typesrenderer.nim``.
  78. assert linkText.kind in {rnRstRef, rnInner}
  79. const NimDefs = ["proc", "func", "macro", "method", "iterator",
  80. "template", "converter", "const", "type", "var",
  81. "enum", "object", "tuple"]
  82. template resolveSymKind(x: string) =
  83. if x in ["enum", "object", "tuple"]:
  84. result.symKind = "type"
  85. result.symTypeKind = x
  86. else:
  87. result.symKind = x
  88. type
  89. State = enum
  90. inBeginning
  91. afterSymKind
  92. beforeSymbolName # auxiliary state to catch situations like `proc []`_ after space
  93. atSymbolName
  94. afterSymbolName
  95. genericsPar
  96. parameterName
  97. parameterType
  98. outType
  99. var state = inBeginning
  100. var curIdent = ""
  101. template flushIdent() =
  102. if curIdent != "":
  103. case state
  104. of inBeginning: doAssert false, "incorrect state inBeginning"
  105. of afterSymKind: resolveSymKind curIdent
  106. of beforeSymbolName: doAssert false, "incorrect state beforeSymbolName"
  107. of atSymbolName: result.name = curIdent.nimIdentBackticksNormalize
  108. of afterSymbolName: doAssert false, "incorrect state afterSymbolName"
  109. of genericsPar: result.generics = curIdent
  110. of parameterName: result.parameters.add (curIdent, "")
  111. of parameterType:
  112. for a in countdown(result.parameters.len - 1, 0):
  113. if result.parameters[a].`type` == "":
  114. result.parameters[a].`type` = curIdent
  115. of outType: result.outType = curIdent
  116. curIdent = ""
  117. var parens = 0
  118. let L = linkText.sons.len
  119. template s(i: int): string = linkText.sons[i].text
  120. var i = 0
  121. template nextState =
  122. case s(i)
  123. of " ":
  124. if state == afterSymKind:
  125. flushIdent
  126. state = beforeSymbolName
  127. of "`":
  128. curIdent.add "`"
  129. inc i
  130. while i < L: # add contents between ` ` as a whole
  131. curIdent.add s(i)
  132. if s(i) == "`":
  133. break
  134. inc i
  135. curIdent = curIdent.nimIdentBackticksNormalize
  136. if state in {inBeginning, afterSymKind, beforeSymbolName}:
  137. state = atSymbolName
  138. flushIdent
  139. state = afterSymbolName
  140. of "[":
  141. if state notin {inBeginning, afterSymKind, beforeSymbolName}:
  142. inc parens
  143. if state in {inBeginning, afterSymKind, beforeSymbolName}:
  144. state = atSymbolName
  145. curIdent.add s(i)
  146. elif state in {atSymbolName, afterSymbolName} and parens == 1:
  147. flushIdent
  148. state = genericsPar
  149. curIdent.add s(i)
  150. else: curIdent.add s(i)
  151. of "]":
  152. if state notin {inBeginning, afterSymKind, beforeSymbolName, atSymbolName}:
  153. dec parens
  154. if state == genericsPar and parens == 0:
  155. curIdent.add s(i)
  156. flushIdent
  157. else: curIdent.add s(i)
  158. of "(":
  159. inc parens
  160. if state in {inBeginning, afterSymKind, beforeSymbolName}:
  161. result.parametersProvided = true
  162. state = atSymbolName
  163. flushIdent
  164. state = parameterName
  165. elif state in {atSymbolName, afterSymbolName, genericsPar} and parens == 1:
  166. result.parametersProvided = true
  167. flushIdent
  168. state = parameterName
  169. else: curIdent.add s(i)
  170. of ")":
  171. dec parens
  172. if state in {parameterName, parameterType} and parens == 0:
  173. flushIdent
  174. state = outType
  175. else: curIdent.add s(i)
  176. of "{": # remove pragmas
  177. while i < L:
  178. if s(i) == "}":
  179. break
  180. inc i
  181. of ",", ";":
  182. if state in {parameterName, parameterType} and parens == 1:
  183. flushIdent
  184. state = parameterName
  185. else: curIdent.add s(i)
  186. of "*": # skip export symbol
  187. if state == atSymbolName:
  188. flushIdent
  189. state = afterSymbolName
  190. elif state == afterSymbolName:
  191. discard
  192. else: curIdent.add "*"
  193. of ":":
  194. if state == outType: discard
  195. elif state == parameterName:
  196. flushIdent
  197. state = parameterType
  198. else: curIdent.add ":"
  199. else:
  200. let isPostfixSymKind = i > 0 and i == L - 1 and
  201. result.symKind == "" and s(i) in NimDefs
  202. if isPostfixSymKind: # for links like `foo proc`_
  203. resolveSymKind s(i)
  204. else:
  205. case state
  206. of inBeginning:
  207. if s(i) in NimDefs:
  208. state = afterSymKind
  209. else:
  210. state = atSymbolName
  211. curIdent.add s(i)
  212. of afterSymKind, beforeSymbolName:
  213. state = atSymbolName
  214. curIdent.add s(i)
  215. of parameterType:
  216. case s(i)
  217. of "ref": curIdent.add "ref."
  218. of "ptr": curIdent.add "ptr."
  219. of "var": discard
  220. else: curIdent.add s(i).nimIdentBackticksNormalize
  221. of atSymbolName:
  222. curIdent.add s(i)
  223. else:
  224. curIdent.add s(i).nimIdentBackticksNormalize
  225. while i < L:
  226. nextState
  227. inc i
  228. if state == afterSymKind: # treat `type`_ as link to symbol `type`
  229. state = atSymbolName
  230. flushIdent
  231. result.isGroup = false
  232. proc match*(generated: LangSymbol, docLink: LangSymbol): bool =
  233. ## Returns true if `generated` can be a target for `docLink`.
  234. ## If `generated` is an overload group then only `symKind` and `name`
  235. ## are compared for success.
  236. result = true
  237. if docLink.symKind != "":
  238. if generated.symKind == "proc":
  239. result = docLink.symKind in ["proc", "func"]
  240. else:
  241. result = generated.symKind == docLink.symKind
  242. if result and docLink.symKind == "type" and docLink.symTypeKind != "":
  243. result = generated.symTypeKind == docLink.symTypeKind
  244. if not result: return
  245. result = generated.name == docLink.name
  246. if not result: return
  247. if generated.isGroup:
  248. # if `()` were added then it's not a reference to the whole group:
  249. return not docLink.parametersProvided
  250. if docLink.generics != "":
  251. result = generated.generics == docLink.generics
  252. if not result: return
  253. if docLink.outType != "":
  254. result = generated.outType == docLink.outType
  255. if not result: return
  256. if docLink.parametersProvided:
  257. result = generated.parameters.len == docLink.parameters.len
  258. if not result: return
  259. var onlyType = false
  260. for i in 0 ..< generated.parameters.len:
  261. let g = generated.parameters[i]
  262. let d = docLink.parameters[i]
  263. if i == 0:
  264. if g.`type` == d.name:
  265. onlyType = true # only types, not names, are provided in `docLink`
  266. if onlyType:
  267. result = g.`type` == d.name:
  268. else:
  269. if d.`type` != "":
  270. result = g.`type` == d.`type`
  271. if not result: return
  272. result = g.name == d.name
  273. if not result: return