summary refs log tree commit diff stats
path: root/compiler/layouter.nim
diff options
context:
space:
mode:
authorMiran <narimiran@disroot.org>2019-06-26 15:36:44 +0200
committerAndreas Rumpf <rumpf_a@web.de>2019-06-26 15:36:44 +0200
commitf288e1b11ab3c796b213fe139f6dcb9c1897218f (patch)
tree620e0b96e9ddb34090b0bea36ab34111114eb6f6 /compiler/layouter.nim
parent0a81b038fd8b75d9d0e29a6b50567897583031ba (diff)
downloadNim-f288e1b11ab3c796b213fe139f6dcb9c1897218f.tar.gz
[bugfix] fix #11469, new rules for a newline in nimpretty (#11512)
* [bugfix] fix #11469, new rules for a newline in nimpretty
* concatenate two lines if they have the same indentation level
Diffstat (limited to 'compiler/layouter.nim')
-rw-r--r--compiler/layouter.nim121
1 files changed, 69 insertions, 52 deletions
diff --git a/compiler/layouter.nim b/compiler/layouter.nim
index ccd0925bf..112c57542 100644
--- a/compiler/layouter.nim
+++ b/compiler/layouter.nim
@@ -16,6 +16,7 @@ from sequtils import delete
 
 const
   MaxLineLen = 80
+  MinLineLen = 10
 
 type
   SplitKind = enum
@@ -25,7 +26,7 @@ type
     detectSemicolonKind, useSemicolon, dontTouch
 
   LayoutToken = enum
-    ltSpaces, ltNewline, ltTab,
+    ltSpaces, ltNewline, ltTab, ltOptionalNewline,
     ltComment, ltLit, ltKeyword, ltExportMarker, ltIdent,
     ltOther, ltOpr,
     ltBeginSection, ltEndSection
@@ -88,6 +89,46 @@ proc computeRhs(em: Emitter; pos: int): int =
     inc result, em.tokens[p].len
     inc p
 
+proc isLongEnough(lineLen, startPos, endPos: int): bool =
+  result = lineLen > MinLineLen and endPos > startPos + 4
+
+proc findNewline(em: Emitter; p, lineLen: var int) =
+  while p < em.tokens.len and em.kinds[p] != ltNewline:
+    inc lineLen, em.tokens[p].len
+    inc p
+
+proc optionalIsGood(em: var Emitter; pos: int): bool =
+  let ourIndent = em.tokens[pos].len
+  var p = pos+1
+  var lineLen = 0
+  em.findNewline(p, lineLen)
+  if p+1 < em.tokens.len and em.kinds[p+1] == ltSpaces and
+      em.kinds[p-1] == ltOptionalNewline:
+    if em.tokens[p+1].len == ourIndent:
+      # concatenate lines with the same indententation
+      var nlPos = p
+      var lineLenTotal = lineLen
+      inc p
+      em.findNewline(p, lineLenTotal)
+      if isLongEnough(lineLenTotal, nlPos, p):
+        em.kinds[nlPos] = ltOptionalNewline
+        if em.kinds[nlPos+1] == ltSpaces:
+          # inhibit extra spaces when concatenating two lines
+          em.tokens[nlPos+1] = if em.tokens[nlPos-2] == ",": " " else: ""
+      result = true
+    elif em.tokens[p+1].len < ourIndent:
+      result = isLongEnough(lineLen, pos, p)
+  elif em.kinds[pos+1] == ltOther: # note: pos+1, not p+1
+    result = false
+  else:
+    result = isLongEnough(lineLen, pos, p)
+
+proc lenOfNextTokens(em: Emitter; pos: int): int =
+  result = 0
+  for i in 1 ..< em.tokens.len-pos:
+    if em.kinds[pos+i] in {ltNewline, ltOptionalNewline}: break
+    inc result, em.tokens[pos+i].len
+
 proc closeEmitter*(em: var Emitter) =
   let outFile = em.config.absOutFile
 
@@ -95,7 +136,8 @@ proc closeEmitter*(em: var Emitter) =
   var maxLhs = 0
   var lineLen = 0
   var lineBegin = 0
-  for i in 0..em.tokens.high:
+  var i = 0
+  while i <= em.tokens.high:
     case em.kinds[i]
     of ltBeginSection:
       maxLhs = computeMax(em, lineBegin)
@@ -113,9 +155,24 @@ proc closeEmitter*(em: var Emitter) =
       content.add em.tokens[i]
       lineLen = 0
       lineBegin = i+1
+    of ltOptionalNewline:
+      let totalLineLen = lineLen + lenOfNextTokens(em, i)
+      if totalLineLen > MaxLineLen + MinLineLen or
+         totalLineLen > MaxLineLen and optionalIsGood(em, i):
+        if i-1 >= 0 and em.kinds[i-1] == ltSpaces:
+          let spaces = em.tokens[i-1].len
+          content.setLen(content.len - spaces)
+        content.add "\L"
+        content.add em.tokens[i]
+        lineLen = em.tokens[i].len
+        lineBegin = i+1
+        if i+1 < em.kinds.len and em.kinds[i+1] == ltSpaces:
+          # inhibit extra spaces at the start of a new line
+          inc i
     else:
       content.add em.tokens[i]
       inc lineLen, em.tokens[i].len
+    inc i
 
   if fileExists(outFile) and readFile(outFile.string) == content:
     discard "do nothing, see #9499"
@@ -170,7 +227,6 @@ proc removeSpaces(em: var Emitter) =
     setLen(em.kinds, em.kinds.len-1)
     dec em.col, tokenLen
 
-template goodCol(col): bool = col in 40..MaxLineLen
 
 const
   openPars = {tkParLe, tkParDotLe,
@@ -184,49 +240,17 @@ const
   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.tokens.len
+template goodCol(col): bool = col >= MaxLineLen div 2
 
 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:
-      # bug #10295, check first if even more indentation would help:
-      let spaces = em.indentLevel+moreIndent(em)
-      if spaces < em.col:
-        removeSpaces em
-        wrNewline(em)
-        em.col = 0
-        wrSpaces em, spaces
-    else:
-      # search backwards for a good split position:
-      for a in mitems(em.altSplitPos):
-        if a > em.fixedUntil:
-          var spaces = 0
-          while a+spaces < em.kinds.len and em.kinds[a+spaces] == ltSpaces:
-            inc spaces
-          if spaces > 0:
-            delete(em.tokens, a, a+spaces-1)
-            delete(em.kinds, a, a+spaces-1)
-          em.kinds.insert(ltNewline, a)
-          em.tokens.insert("\L", a)
-          em.kinds.insert(ltSpaces, a+1)
-          em.tokens.insert(repeat(' ', em.indentLevel+moreIndent(em)), a+1)
-          # recompute em.col:
-          var i = em.kinds.len-1
-          em.col = 0
-          while i >= 0 and em.kinds[i] != ltNewline:
-            inc em.col, em.tokens[i].len
-            dec i
-          # mark position as "already split here"
-          a = -1
-          break
+  if em.doIndentMore > 0: em.indWidth*2 else: em.indWidth
+
+template rememberSplit(kind) =
+  if goodCol(em.col) and not em.inquote:
+    let spaces = em.indentLevel+moreIndent(em)
+    if spaces < em.col and spaces > 0:
+      wr(em, strutils.repeat(' ', spaces), ltOptionalNewline)
+    #em.altSplitPos[kind] = em.tokens.len
 
 proc emitMultilineComment(em: var Emitter, lit: string, col: int) =
   # re-align every line in the multi-line comment:
@@ -358,14 +382,9 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
 
     if not em.inquote:
       wr(em, TokTypeToStr[tok.tokType], ltKeyword)
-
-      case tok.tokType
-      of tkAnd: rememberSplit(splitAnd)
-      of tkOr: rememberSplit(splitOr)
-      of tkIn, tkNotin:
+      if tok.tokType in {tkAnd, tkOr, tkIn, tkNotin}:
         rememberSplit(splitIn)
         wrSpace em
-      else: discard
     else:
       # keywords in backticks are not normalized:
       wr(em, tok.ident.s, ltIdent)
@@ -422,7 +441,6 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
       emitComment(em, tok)
   of tkIntLit..tkStrLit, tkRStrLit, tkTripleStrLit, tkGStrLit, tkGTripleStrLit, tkCharLit:
     let lit = fileSection(em.config, em.fid, tok.offsetA, tok.offsetB)
-    softLinebreak(em, lit)
     if endsInAlpha(em) and tok.tokType notin {tkGStrLit, tkGTripleStrLit}: wrSpace(em)
     em.lineSpan = countNewlines(lit)
     if em.lineSpan > 0: calcCol(em, lit)
@@ -430,7 +448,6 @@ proc emitTok*(em: var Emitter; L: TLexer; tok: TToken) =
   of tkEof: discard
   else:
     let lit = if tok.ident != nil: tok.ident.s else: tok.literal
-    softLinebreak(em, lit)
     if endsInAlpha(em): wrSpace(em)
     wr em, lit, ltIdent