123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2021 Nim contributors
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## Integration helpers between ``docgen.nim`` and ``rst.nim``.
- ##
- ## Function `toLangSymbol(linkText)`_ produces a signature `docLink` of
- ## `type LangSymbol`_ in ``rst.nim``, while `match(generated, docLink)`_
- ## matches it with `generated`, produced from `PNode` by ``docgen.rst``.
- import rstast
- import std/strutils
- when defined(nimPreviewSlimSystem):
- import std/[assertions, syncio]
- type
- LangSymbol* = object ## symbol signature in Nim
- symKind*: string ## "proc", "const", "type", etc
- symTypeKind*: string ## ""|enum|object|tuple -
- ## valid only when `symKind == "type"`
- name*: string ## plain symbol name without any parameters
- generics*: string ## generic parameters (without brackets)
- isGroup*: bool ## is LangSymbol a group with overloads?
- # the following fields are valid iff `isGroup` == false
- # (always false when parsed by `toLangSymbol` because link like foo_
- # can point to just a single symbol foo, e.g. proc).
- parametersProvided*: bool ## to disambiguate `proc f`_ and `proc f()`_
- parameters*: seq[tuple[name: string, `type`: string]]
- ## name-type seq, e.g. for proc
- outType*: string ## result type, e.g. for proc
- proc `$`*(s: LangSymbol): string = # for debug
- ("(symkind=$1, symTypeKind=$2, name=$3, generics=$4, isGroup=$5, " &
- "parametersProvided=$6, parameters=$7, outType=$8)") % [
- s.symKind, s.symTypeKind , s.name, s.generics, $s.isGroup,
- $s.parametersProvided, $s.parameters, s.outType]
- func nimIdentBackticksNormalize*(s: string): string =
- ## Normalizes the string `s` as a Nim identifier.
- ##
- ## Unlike `nimIdentNormalize` removes spaces and backticks.
- ##
- ## .. Warning:: No checking (e.g. that identifiers cannot start from
- ## digits or '_', or that number of backticks is even) is performed.
- runnableExamples:
- doAssert nimIdentBackticksNormalize("Foo_bar") == "Foobar"
- doAssert nimIdentBackticksNormalize("FoO BAr") == "Foobar"
- doAssert nimIdentBackticksNormalize("`Foo BAR`") == "Foobar"
- doAssert nimIdentBackticksNormalize("` Foo BAR `") == "Foobar"
- # not a valid identifier:
- doAssert nimIdentBackticksNormalize("`_x_y`") == "_xy"
- result = newString(s.len)
- var firstChar = true
- var j = 0
- for i in 0..len(s) - 1:
- if s[i] in {'A'..'Z'}:
- if not firstChar: # to lowercase
- result[j] = chr(ord(s[i]) + (ord('a') - ord('A')))
- else:
- result[j] = s[i]
- firstChar = false
- inc j
- elif s[i] notin {'_', ' ', '`'}:
- result[j] = s[i]
- inc j
- firstChar = false
- elif s[i] == '_' and firstChar:
- result[j] = '_'
- inc j
- firstChar = false
- else: discard # just omit '`' or ' '
- if j != s.len: setLen(result, j)
- proc langSymbolGroup*(kind: string, name: string): LangSymbol =
- if kind notin ["proc", "func", "macro", "method", "iterator",
- "template", "converter"]:
- raise newException(ValueError, "unknown symbol kind $1" % [kind])
- result = LangSymbol(symKind: kind, name: name, isGroup: true)
- proc toLangSymbol*(linkText: PRstNode): LangSymbol =
- ## Parses `linkText` into a more structured form using a state machine.
- ##
- ## This proc is designed to allow link syntax with operators even
- ## without escaped backticks inside:
- ##
- ## `proc *`_
- ## `proc []`_
- ##
- ## This proc should be kept in sync with the `renderTypes` proc from
- ## ``compiler/typesrenderer.nim``.
- template fail(msg: string) =
- raise newException(ValueError, msg)
- if linkText.kind notin {rnRstRef, rnInner}:
- fail("toLangSymbol: wrong input kind " & $linkText.kind)
- const NimDefs = ["proc", "func", "macro", "method", "iterator",
- "template", "converter", "const", "type", "var",
- "enum", "object", "tuple", "module"]
- template resolveSymKind(x: string) =
- if x in ["enum", "object", "tuple"]:
- result.symKind = "type"
- result.symTypeKind = x
- else:
- result.symKind = x
- type
- State = enum
- inBeginning
- afterSymKind
- beforeSymbolName # auxiliary state to catch situations like `proc []`_ after space
- atSymbolName
- afterSymbolName
- genericsPar
- parameterName
- parameterType
- outType
- var state = inBeginning
- var curIdent = ""
- template flushIdent() =
- if curIdent != "":
- case state
- of inBeginning: fail("incorrect state inBeginning")
- of afterSymKind: resolveSymKind curIdent
- of beforeSymbolName: fail("incorrect state beforeSymbolName")
- of atSymbolName: result.name = curIdent.nimIdentBackticksNormalize
- of afterSymbolName: fail("incorrect state afterSymbolName")
- of genericsPar: result.generics = curIdent
- of parameterName: result.parameters.add (curIdent, "")
- of parameterType:
- for a in countdown(result.parameters.len - 1, 0):
- if result.parameters[a].`type` == "":
- result.parameters[a].`type` = curIdent
- of outType: result.outType = curIdent
- curIdent = ""
- var parens = 0
- let L = linkText.sons.len
- template s(i: int): string = linkText.sons[i].text
- var i = 0
- template nextState =
- case s(i)
- of " ":
- if state == afterSymKind:
- flushIdent
- state = beforeSymbolName
- of "`":
- curIdent.add "`"
- inc i
- while i < L: # add contents between ` ` as a whole
- curIdent.add s(i)
- if s(i) == "`":
- break
- inc i
- curIdent = curIdent.nimIdentBackticksNormalize
- if state in {inBeginning, afterSymKind, beforeSymbolName}:
- state = atSymbolName
- flushIdent
- state = afterSymbolName
- of "[":
- if state notin {inBeginning, afterSymKind, beforeSymbolName}:
- inc parens
- if state in {inBeginning, afterSymKind, beforeSymbolName}:
- state = atSymbolName
- curIdent.add s(i)
- elif state in {atSymbolName, afterSymbolName} and parens == 1:
- flushIdent
- state = genericsPar
- curIdent.add s(i)
- else: curIdent.add s(i)
- of "]":
- if state notin {inBeginning, afterSymKind, beforeSymbolName, atSymbolName}:
- dec parens
- if state == genericsPar and parens == 0:
- curIdent.add s(i)
- flushIdent
- else: curIdent.add s(i)
- of "(":
- inc parens
- if state in {inBeginning, afterSymKind, beforeSymbolName}:
- result.parametersProvided = true
- state = atSymbolName
- flushIdent
- state = parameterName
- elif state in {atSymbolName, afterSymbolName, genericsPar} and parens == 1:
- result.parametersProvided = true
- flushIdent
- state = parameterName
- else: curIdent.add s(i)
- of ")":
- dec parens
- if state in {parameterName, parameterType} and parens == 0:
- flushIdent
- state = outType
- else: curIdent.add s(i)
- of "{": # remove pragmas
- while i < L:
- if s(i) == "}":
- break
- inc i
- of ",", ";":
- if state in {parameterName, parameterType} and parens == 1:
- flushIdent
- state = parameterName
- else: curIdent.add s(i)
- of "*": # skip export symbol
- if state == atSymbolName:
- flushIdent
- state = afterSymbolName
- elif state == afterSymbolName:
- discard
- else: curIdent.add "*"
- of ":":
- if state == outType: discard
- elif state == parameterName:
- flushIdent
- state = parameterType
- else: curIdent.add ":"
- else:
- let isPostfixSymKind = i > 0 and i == L - 1 and
- result.symKind == "" and s(i) in NimDefs
- if isPostfixSymKind: # for links like `foo proc`_
- resolveSymKind s(i)
- else:
- case state
- of inBeginning:
- if s(i) in NimDefs:
- state = afterSymKind
- else:
- state = atSymbolName
- curIdent.add s(i)
- of afterSymKind, beforeSymbolName:
- state = atSymbolName
- curIdent.add s(i)
- of parameterType:
- case s(i)
- of "ref": curIdent.add "ref."
- of "ptr": curIdent.add "ptr."
- of "var": discard
- else: curIdent.add s(i).nimIdentBackticksNormalize
- of atSymbolName:
- curIdent.add s(i)
- else:
- curIdent.add s(i).nimIdentBackticksNormalize
- while i < L:
- nextState
- inc i
- if state == afterSymKind: # treat `type`_ as link to symbol `type`
- state = atSymbolName
- flushIdent
- result.isGroup = false
- proc match*(generated: LangSymbol, docLink: LangSymbol): bool =
- ## Returns true if `generated` can be a target for `docLink`.
- ## If `generated` is an overload group then only `symKind` and `name`
- ## are compared for success.
- result = true
- if docLink.symKind != "":
- if generated.symKind == "proc":
- result = docLink.symKind in ["proc", "func"]
- else:
- result = generated.symKind == docLink.symKind
- if result and docLink.symKind == "type" and docLink.symTypeKind != "":
- result = generated.symTypeKind == docLink.symTypeKind
- if not result: return
- result = generated.name == docLink.name
- if not result: return
- if generated.isGroup:
- # if `()` were added then it's not a reference to the whole group:
- return not docLink.parametersProvided
- if docLink.generics != "":
- result = generated.generics == docLink.generics
- if not result: return
- if docLink.outType != "":
- result = generated.outType == docLink.outType
- if not result: return
- if docLink.parametersProvided:
- result = generated.parameters.len == docLink.parameters.len
- if not result: return
- var onlyType = false
- for i in 0 ..< generated.parameters.len:
- let g = generated.parameters[i]
- let d = docLink.parameters[i]
- if i == 0:
- if g.`type` == d.name:
- onlyType = true # only types, not names, are provided in `docLink`
- if onlyType:
- result = g.`type` == d.name
- else:
- if d.`type` != "":
- result = g.`type` == d.`type`
- if not result: return
- result = g.name == d.name
- if not result: return
|