dochelpers.nim 10 KB

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