diff options
Diffstat (limited to 'compiler/renderverbatim.nim')
-rw-r--r-- | compiler/renderverbatim.nim | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/compiler/renderverbatim.nim b/compiler/renderverbatim.nim new file mode 100644 index 000000000..c12595156 --- /dev/null +++ b/compiler/renderverbatim.nim @@ -0,0 +1,137 @@ +import std/strutils + +import ast, options, msgs + +when defined(nimPreviewSlimSystem): + import std/assertions + + +const isDebug = false +when isDebug: + import renderer + import astalgo + +proc lastNodeRec(n: PNode): PNode = + result = n + while result.safeLen > 0: result = result[^1] + +proc isInIndentationBlock(src: string, indent: int): bool = + #[ + we stop at the first de-indentation; there's an inherent ambiguity with non + doc comments since they can have arbitrary indentation, so we just take the + practical route and require a runnableExamples to keep its code (including non + doc comments) to its indentation level. + ]# + for j in 0..<indent: + if src.len <= j: return true + if src[j] != ' ': return false + return true + +type LineData = object + ## keep track of which lines are starting inside a multiline doc comment. + ## We purposefully avoid re-doing parsing which is already done (we get a PNode) + ## so we don't worry about whether we're inside (nested) doc comments etc. + ## But we sill need some logic to disambiguate different multiline styles. + conf: ConfigRef + lineFirst: int + lines: seq[bool] + ## lines[index] is true if line `lineFirst+index` starts inside a multiline string + ## Using a HashSet (extra dependency) would simplify but not by much. + +proc tripleStrLitStartsAtNextLine(conf: ConfigRef, n: PNode): bool = + # enabling TLineInfo.offsetA,offsetB would probably make this easier + result = false + const tripleQuote = "\"\"\"" + let src = sourceLine(conf, n.info) + let col = n.info.col + doAssert src.continuesWith(tripleQuote, col) # sanity check + var i = col + 3 + var onlySpace = true + while true: + if src.len <= i: + doAssert src.len == i + return onlySpace + elif src.continuesWith(tripleQuote, i) and (src.len == i+3 or src[i+3] != '\"'): + return false # triple lit is in 1 line + elif src[i] != ' ': onlySpace = false + i.inc + +proc visitMultilineStrings(ldata: var LineData, n: PNode) = + var cline = ldata.lineFirst + + template setLine() = + let index = cline - ldata.lineFirst + if ldata.lines.len < index+1: ldata.lines.setLen index+1 + ldata.lines[index] = true + + case n.kind + of nkTripleStrLit: + # same logic should be applied for any multiline token + # we could also consider nkCommentStmt but right now we just assume doc comments, + # unlike triple string litterals, don't de-indent from runnableExamples. + cline = n.info.line.int + if tripleStrLitStartsAtNextLine(ldata.conf, n): + cline.inc + setLine() + for ai in n.strVal: + case ai + of '\n': + cline.inc + setLine() + else: discard + else: + for i in 0..<n.safeLen: + visitMultilineStrings(ldata, n[i]) + +proc startOfLineInsideTriple(ldata: LineData, line: int): bool = + let index = line - ldata.lineFirst + if index >= ldata.lines.len: false + else: ldata.lines[index] + +proc extractRunnableExamplesSource*(conf: ConfigRef; n: PNode, indent = 0): string = + ## TLineInfo.offsetA,offsetB would be cleaner but it's only enabled for nimpretty, + ## we'd need to check performance impact to enable it for nimdoc. + var first = n.lastSon.info + if first.line == n[0].info.line: + #[ + runnableExamples: assert true + ]# + discard + else: + #[ + runnableExamples: + # non-doc comment that we want to capture even though `first` points to `assert true` + assert true + ]# + first.line = n[0].info.line + 1 + + let last = n.lastNodeRec.info + var info = first + var indent2 = info.col + let numLines = numLines(conf, info.fileIndex).uint16 + var lastNonemptyPos = 0 + + var ldata = LineData(lineFirst: first.line.int, conf: conf) + visitMultilineStrings(ldata, n[^1]) + when isDebug: + debug(n) + for i in 0..<ldata.lines.len: + echo (i+ldata.lineFirst, ldata.lines[i]) + + result = "" + for line in first.line..numLines: # bugfix, see `testNimDocTrailingExample` + info.line = line + let src = sourceLine(conf, info) + let special = startOfLineInsideTriple(ldata, line.int) + if line > last.line and not special and not isInIndentationBlock(src, indent2): + break + if line > first.line: result.add "\n" + if special: + result.add src + lastNonemptyPos = result.len + elif src.len > indent2: + for i in 0..<indent: result.add ' ' + result.add src[indent2..^1] + lastNonemptyPos = result.len + result.setLen lastNonemptyPos + |