dochelpers.nim 10 KB

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