diff options
author | Andreas Rumpf <rumpf_a@web.de> | 2018-06-26 18:33:51 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-06-26 18:33:51 +0200 |
commit | 2a3a128e362929bac9c2dbf7430cbe8732840f95 (patch) | |
tree | ac794774866b8b71c5a6e271a21b42b060b57900 /compiler/layouter.nim | |
parent | e129466910b6efa730f8d5d9232efbce6dae46f0 (diff) | |
parent | d08b9eb6731a70504be6d856723fbc94dc7bd506 (diff) | |
download | Nim-2a3a128e362929bac9c2dbf7430cbe8732840f95.tar.gz |
Merge branch 'devel' into typedesc-reforms
Diffstat (limited to 'compiler/layouter.nim')
-rw-r--r-- | compiler/layouter.nim | 164 |
1 files changed, 115 insertions, 49 deletions
diff --git a/compiler/layouter.nim b/compiler/layouter.nim index 90e9d6fd7..36ad08696 100644 --- a/compiler/layouter.nim +++ b/compiler/layouter.nim @@ -7,11 +7,7 @@ # distribution, for details about the copyright. # -## Layouter for nimpretty. Still primitive but useful. -## TODO -## - Fix 'echo ()' vs 'echo()' difference! -## - Make indentations consistent. -## - Align 'if' and 'case' expressions properly. +## Layouter for nimpretty. import idents, lexer, lineinfos, llstream, options, msgs, strutils from os import changeFileExt @@ -24,32 +20,44 @@ type SplitKind = enum splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary + SemicolonKind = enum + detectSemicolonKind, useSemicolon, dontTouch + Emitter* = object - f: PLLStream config: ConfigRef fid: FileIndex lastTok: TTokType inquote: bool - col, lastLineNumber, lineSpan, indentLevel: int + semicolons: SemicolonKind + col, lastLineNumber, lineSpan, indentLevel, indWidth: int + nested: int + doIndentMore*: int content: string + indentStack: seq[int] fixedUntil: int # marks where we must not go in the content altSplitPos: array[SplitKind, int] # alternative split positions -proc openEmitter*(em: var Emitter, config: ConfigRef, fileIdx: FileIndex) = - let outfile = changeFileExt(config.toFullPath(fileIdx), ".pretty.nim") - em.f = llStreamOpen(outfile, fmWrite) +proc openEmitter*(em: var Emitter, cache: IdentCache; + config: ConfigRef, fileIdx: FileIndex) = + let fullPath = config.toFullPath(fileIdx) + em.indWidth = getIndentWidth(fileIdx, llStreamOpen(fullPath, fmRead), + cache, config) + if em.indWidth == 0: em.indWidth = 2 em.config = config em.fid = fileIdx em.lastTok = tkInvalid em.inquote = false em.col = 0 em.content = newStringOfCap(16_000) - if em.f == nil: - rawMessage(config, errGenerated, "cannot open file: " & outfile) + em.indentStack = newSeqOfCap[int](30) + em.indentStack.add 0 proc closeEmitter*(em: var Emitter) = - em.f.llStreamWrite em.content - llStreamClose(em.f) + var f = llStreamOpen(em.config.outFile, fmWrite) + if f == nil: + rawMessage(em.config, errGenerated, "cannot open file: " & em.config.outFile) + f.llStreamWrite em.content + llStreamClose(f) proc countNewlines(s: string): int = result = 0 @@ -69,28 +77,41 @@ template wr(x) = template goodCol(col): bool = col in 40..MaxLineLen -const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, - tkBracketLe, tkBracketLeColon, tkCurlyDotLe, - tkCurlyLe} +const + openPars = {tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + splitters = openPars + {tkComma, tkSemicolon} + oprSet = {tkOpr, tkDiv, tkMod, tkShl, tkShr, tkIn, tkNotin, tkIs, + tkIsnot, tkNot, tkOf, tkAs, tkDotDot, tkAnd, tkOr, tkXor} template rememberSplit(kind) = if goodCol(em.col): em.altSplitPos[kind] = em.content.len +template moreIndent(em): int = + (if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth) + proc softLinebreak(em: var Emitter, lit: string) = # XXX Use an algorithm that is outlined here: # https://llvm.org/devmtg/2013-04/jasper-slides.pdf # +2 because we blindly assume a comma or ' &' might follow if not em.inquote and em.col+lit.len+2 >= MaxLineLen: if em.lastTok in splitters: + while em.content.len > 0 and em.content[em.content.high] == ' ': + setLen(em.content, em.content.len-1) wr("\L") em.col = 0 - for i in 1..em.indentLevel+2: wr(" ") + for i in 1..em.indentLevel+moreIndent(em): wr(" ") else: # search backwards for a good split position: for a in em.altSplitPos: if a > em.fixedUntil: - let ws = "\L" & repeat(' ',em.indentLevel+2) + var spaces = 0 + while a+spaces < em.content.len and em.content[a+spaces] == ' ': + inc spaces + if spaces > 0: delete(em.content, a, a+spaces-1) + let ws = "\L" & repeat(' ',em.indentLevel+moreIndent(em)) em.col = em.content.len - a em.content.insert(ws, a) break @@ -98,7 +119,7 @@ proc softLinebreak(em: var Emitter, lit: string) = proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = template endsInWhite(em): bool = - em.content.len > 0 and em.content[em.content.high] in {' ', '\L'} + em.content.len == 0 or em.content[em.content.high] in {' ', '\L'} template endsInAlpha(em): bool = em.content.len > 0 and em.content[em.content.high] in SymChars+{'_'} @@ -120,14 +141,32 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = em.fixedUntil = em.content.high elif tok.indent >= 0: - em.indentLevel = tok.indent + if em.lastTok in (splitters + oprSet): + em.indentLevel = tok.indent + else: + if tok.indent > em.indentStack[^1]: + em.indentStack.add tok.indent + else: + # dedent? + while em.indentStack.len > 1 and em.indentStack[^1] > tok.indent: + discard em.indentStack.pop() + em.indentLevel = em.indentStack.high * em.indWidth + #[ we only correct the indentation if it is not in an expression context, + so that code like + + const splitters = {tkComma, tkSemicolon, tkParLe, tkParDotLe, + tkBracketLe, tkBracketLeColon, tkCurlyDotLe, + tkCurlyLe} + + is not touched. + ]# # remove trailing whitespace: while em.content.len > 0 and em.content[em.content.high] == ' ': setLen(em.content, em.content.len-1) wr("\L") for i in 2..tok.line - em.lastLineNumber: wr("\L") em.col = 0 - for i in 1..tok.indent: + for i in 1..em.indentLevel: wr(" ") em.fixedUntil = em.content.high @@ -135,49 +174,64 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) = of tokKeywordLow..tokKeywordHigh: if endsInAlpha(em): wr(" ") - elif not em.inquote and not endsInWhite(em): + elif not em.inquote and not endsInWhite(em) and + em.lastTok notin openPars: + #and tok.tokType in oprSet wr(" ") - wr(TokTypeToStr[tok.tokType]) + if not em.inquote: + wr(TokTypeToStr[tok.tokType]) - case tok.tokType - of tkAnd: rememberSplit(splitAnd) - of tkOr: rememberSplit(splitOr) - of tkIn, tkNotin: - rememberSplit(splitIn) - wr(" ") - else: discard + case tok.tokType + of tkAnd: rememberSplit(splitAnd) + of tkOr: rememberSplit(splitOr) + of tkIn, tkNotin: + rememberSplit(splitIn) + wr(" ") + else: discard + else: + # keywords in backticks are not normalized: + wr(tok.ident.s) of tkColon: wr(TokTypeToStr[tok.tokType]) wr(" ") of tkSemicolon, tkComma: wr(TokTypeToStr[tok.tokType]) - wr(" ") rememberSplit(splitComma) - of tkParLe, tkParRi, tkBracketLe, - tkBracketRi, tkCurlyLe, tkCurlyRi, - tkBracketDotLe, tkBracketDotRi, - tkCurlyDotLe, tkCurlyDotRi, - tkParDotLe, tkParDotRi, - tkColonColon, tkDot, tkBracketLeColon: + wr(" ") + of tkParDotLe, tkParLe, tkBracketDotLe, tkBracketLe, + tkCurlyLe, tkCurlyDotLe, tkBracketLeColon: + if tok.strongSpaceA > 0 and not em.endsInWhite: + wr(" ") + wr(TokTypeToStr[tok.tokType]) + rememberSplit(splitParLe) + of tkParRi, + tkBracketRi, tkCurlyRi, + tkBracketDotRi, + tkCurlyDotRi, + tkParDotRi, + tkColonColon, tkDot: wr(TokTypeToStr[tok.tokType]) - if tok.tokType in splitters: - rememberSplit(splitParLe) of tkEquals: - if not em.endsInWhite: wr(" ") + if not em.inquote and not em.endsInWhite: wr(" ") wr(TokTypeToStr[tok.tokType]) - wr(" ") + if not em.inquote: wr(" ") of tkOpr, tkDotDot: - if not em.endsInWhite: wr(" ") - wr(tok.ident.s) - template isUnary(tok): bool = - tok.strongSpaceB == 0 and tok.strongSpaceA > 0 + if tok.strongSpaceA == 0 and tok.strongSpaceB == 0: + # if not surrounded by whitespace, don't produce any whitespace either: + wr(tok.ident.s) + else: + if not em.endsInWhite: wr(" ") + wr(tok.ident.s) + template isUnary(tok): bool = + tok.strongSpaceB == 0 and tok.strongSpaceA > 0 - if not isUnary(tok) or em.lastTok in {tkOpr, tkDotDot}: - wr(" ") - rememberSplit(splitBinary) + if not isUnary(tok): + wr(" ") + rememberSplit(splitBinary) of tkAccent: + if not em.inquote and endsInAlpha(em): wr(" ") wr(TokTypeToStr[tok.tokType]) em.inquote = not em.inquote of tkComment: @@ -206,3 +260,15 @@ proc starWasExportMarker*(em: var Emitter) = setLen(em.content, em.content.len-3) em.content.add("*") dec em.col, 2 + +proc commaWasSemicolon*(em: var Emitter) = + if em.semicolons == detectSemicolonKind: + em.semicolons = if em.content.endsWith(", "): dontTouch else: useSemicolon + if em.semicolons == useSemicolon and em.content.endsWith(", "): + setLen(em.content, em.content.len-2) + em.content.add("; ") + +proc curlyRiWasPragma*(em: var Emitter) = + if em.content.endsWith("}"): + setLen(em.content, em.content.len-1) + em.content.add(".}") |