renderverbatim.nim 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. import strutils
  2. import ast, options, msgs
  3. const isDebug = false
  4. when isDebug:
  5. import renderer
  6. import astalgo
  7. proc lastNodeRec(n: PNode): PNode =
  8. result = n
  9. while result.safeLen > 0: result = result[^1]
  10. proc isInIndentationBlock(src: string, indent: int): bool =
  11. #[
  12. we stop at the first de-indentation; there's an inherent ambiguity with non
  13. doc comments since they can have arbitrary indentation, so we just take the
  14. practical route and require a runnableExamples to keep its code (including non
  15. doc comments) to its indentation level.
  16. ]#
  17. for j in 0..<indent:
  18. if src.len <= j: return true
  19. if src[j] != ' ': return false
  20. return true
  21. type LineData = object
  22. ## keep track of which lines are starting inside a multiline doc comment.
  23. ## We purposefully avoid re-doing parsing which is already done (we get a PNode)
  24. ## so we don't worry about whether we're inside (nested) doc comments etc.
  25. ## But we sill need some logic to disambiguate different multiline styles.
  26. conf: ConfigRef
  27. lineFirst: int
  28. lines: seq[bool]
  29. ## lines[index] is true if line `lineFirst+index` starts inside a multiline string
  30. ## Using a HashSet (extra dependency) would simplify but not by much.
  31. proc tripleStrLitStartsAtNextLine(conf: ConfigRef, n: PNode): bool =
  32. # enabling TLineInfo.offsetA,offsetB would probably make this easier
  33. const tripleQuote = "\"\"\""
  34. let src = sourceLine(conf, n.info)
  35. let col = n.info.col
  36. doAssert src.continuesWith(tripleQuote, col) # sanity check
  37. var i = col + 3
  38. var onlySpace = true
  39. while true:
  40. if src.len <= i:
  41. doAssert src.len == i
  42. return onlySpace
  43. elif src.continuesWith(tripleQuote, i) and (src.len == i+3 or src[i+3] != '\"'):
  44. return false # triple lit is in 1 line
  45. elif src[i] != ' ': onlySpace = false
  46. i.inc
  47. proc visitMultilineStrings(ldata: var LineData, n: PNode) =
  48. var cline = ldata.lineFirst
  49. template setLine() =
  50. let index = cline - ldata.lineFirst
  51. if ldata.lines.len < index+1: ldata.lines.setLen index+1
  52. ldata.lines[index] = true
  53. case n.kind
  54. of nkTripleStrLit:
  55. # same logic should be applied for any multiline token
  56. # we could also consider nkCommentStmt but right now we just assume doc comments,
  57. # unlike triple string litterals, don't de-indent from runnableExamples.
  58. cline = n.info.line.int
  59. if tripleStrLitStartsAtNextLine(ldata.conf, n):
  60. cline.inc
  61. setLine()
  62. for ai in n.strVal:
  63. case ai
  64. of '\n':
  65. cline.inc
  66. setLine()
  67. else: discard
  68. else:
  69. for i in 0..<n.safeLen:
  70. visitMultilineStrings(ldata, n[i])
  71. proc startOfLineInsideTriple(ldata: LineData, line: int): bool =
  72. let index = line - ldata.lineFirst
  73. if index >= ldata.lines.len: false
  74. else: ldata.lines[index]
  75. proc extractRunnableExamplesSource*(conf: ConfigRef; n: PNode, indent = 0): string =
  76. ## TLineInfo.offsetA,offsetB would be cleaner but it's only enabled for nimpretty,
  77. ## we'd need to check performance impact to enable it for nimdoc.
  78. var first = n.lastSon.info
  79. if first.line == n[0].info.line:
  80. #[
  81. runnableExamples: assert true
  82. ]#
  83. discard
  84. else:
  85. #[
  86. runnableExamples:
  87. # non-doc comment that we want to capture even though `first` points to `assert true`
  88. assert true
  89. ]#
  90. first.line = n[0].info.line + 1
  91. let last = n.lastNodeRec.info
  92. var info = first
  93. var indent2 = info.col
  94. let numLines = numLines(conf, info.fileIndex).uint16
  95. var lastNonemptyPos = 0
  96. var ldata = LineData(lineFirst: first.line.int, conf: conf)
  97. visitMultilineStrings(ldata, n[^1])
  98. when isDebug:
  99. debug(n)
  100. for i in 0..<ldata.lines.len:
  101. echo (i+ldata.lineFirst, ldata.lines[i])
  102. result = ""
  103. for line in first.line..numLines: # bugfix, see `testNimDocTrailingExample`
  104. info.line = line
  105. let src = sourceLine(conf, info)
  106. let special = startOfLineInsideTriple(ldata, line.int)
  107. if line > last.line and not special and not isInIndentationBlock(src, indent2):
  108. break
  109. if line > first.line: result.add "\n"
  110. if special:
  111. result.add src
  112. lastNonemptyPos = result.len
  113. elif src.len > indent2:
  114. for i in 0..<indent: result.add ' '
  115. result.add src[indent2..^1]
  116. lastNonemptyPos = result.len
  117. result.setLen lastNonemptyPos