# # # The Nim Compiler # (c) Copyright 2018 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Layouter for nimpretty. import idents, lexer, lineinfos, llstream, options, msgs, strutils, pathutils const MinLineLen = 15 type SplitKind = enum splitComma, splitParLe, splitAnd, splitOr, splitIn, splitBinary SemicolonKind = enum detectSemicolonKind, useSemicolon, dontTouch LayoutToken* = enum ltSpaces, ltCrucialNewline, ## a semantically crucial newline (indentation!) ltSplittingNewline, ## newline used for splitting up long ## expressions (like after a comma or a binary operator) ltTab, ltOptionalNewline, ## optional newline introduced by nimpretty ltComment, ltLit, ltKeyword, ltExportMarker, ltIdent, ltOther, ltOpr, ltSomeParLe, ltSomeParRi, ltBeginSection, ltEndSection Emitter* = object config: ConfigRef fid: FileIndex lastTok: TTokType inquote, lastTokWasTerse: bool semicolons: SemicolonKind col, lastLineNumber, lineSpan, indentLevel, indWidth*, inSection: int keepIndents*: int doIndentMore*: int kinds*: seq[LayoutToken] tokens*: seq[string] indentStack: seq[int] fixedUntil: int # marks where we must not go in the content altSplitPos: array[SplitKind, int] # alternative split positions maxLineLen*: int proc openEmitter*(em: var Emitter, cache: IdentCache; config: ConfigRef, fileIdx: FileIndex) = let fullPath = AbsoluteFile config.toFullPath(fileIdx) if em.indWidth == 0: 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.indentStack = newSeqOfCap[int](30) em.indentStack.add 0 em.lastLineNumber = 1 proc computeMax(em: Emitter; pos: int): int = var p = pos var extraSpace = 0 result = 0 while p < em.tokens.len and em.kinds[p] != ltEndSection: var lhs = 0 var lineLen = 0 var foundTab = false while p < em.tokens.len and em.kinds[p] != ltEndSection: if em.kinds[p] in {ltCrucialNewline, ltSplittingNewline}: if foundTab and lineLen <= em.maxLineLen: result = max(result, lhs + extraSpace) inc p break if em.kinds[p] == ltTab: extraSpace = if em.kinds[p-1] == ltSpaces: 0 else: 1 foundTab = true else: if not foundTab: inc lhs, em.tokens[p].len inc lineLen, em.tokens[p].len inc p proc computeRhs(em: Emitter; pos: int): int = var p = pos result = 0 while p < em.tokens.len and em.kinds[p] notin {ltCrucialNewline, ltSplittingNewline}: 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] notin {ltCrucialNewline, ltSplittingNewline}: inc lineLen, em.tokens[p].len inc p proc countNewlines(s: string): int = result = 0 for i in 0..= 0 and s[i] != '\L': dec i inc em.col proc optionalIsGood(em: var Emitter; pos, currentLen: int): bool = let ourIndent = em.tokens[pos].len var p = pos+1 var lineLen = 0 em.findNewline(p, lineLen) if p == pos+1: # optionalNewline followed by another newline result = false elif em.kinds[p-1] == ltComment and currentLen+lineLen < em.maxLineLen+MinLineLen: result = false elif 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] in {ltOther, ltSomeParLe, ltSomeParRi}: # 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 {ltCrucialNewline, ltSplittingNewline, ltOptionalNewline}: break inc result, em.tokens[pos+i].len proc guidingInd(em: Emitter; pos: int): int = var i = pos - 1 while i >= 0 and em.kinds[i] != ltSomeParLe: dec i while i+1 <= em.kinds.high and em.kinds[i] != ltSomeParRi: if em.kinds[i] == ltSplittingNewline and em.kinds[i+1] == ltSpaces: return em.tokens[i+1].len inc i result = -1 proc renderTokens*(em: var Emitter): string = ## Render Emitter tokens to a string of code template defaultCase() = content.add em.tokens[i] inc lineLen, em.tokens[i].len var content = newStringOfCap(16_000) var maxLhs = 0 var lineLen = 0 var lineBegin = 0 var openPars = 0 var i = 0 while i <= em.t
# test a simple yet highly efficient set of strings

type
  TRadixNodeKind = enum rnLinear, rnFull, rnLeaf
  PRadixNode = ref TRadixNode
  TRadixNode = object {.inheritable.}
    kind: TRadixNodeKind
  TRadixNodeLinear = object of TRadixNode
    len: int8
    keys: array[0..31, char]
    vals: array[0..31, PRadixNode]
  TRadixNodeFull = object of TRadixNode
    b: array[char, PRadixNode]
  TRadixNodeLeaf = object of TRadixNode
    s: string
  PRadixNodeLinear = ref TRadixNodeLinear
  PRadixNodeFull = ref TRadixNodeFull
  PRadixNodeLeaf = ref TRadixNodeLeaf

proc search(r: PRadixNode, s: string): PRadixNode =
  var r = r
  var i = 0
  while r != nil:
    case r.kind
    of rnLinear:
      var x = PRadixNodeLinear(r)
      for j in 0..ze(x.len)-1:
        if x.keys[j] == s[i]:
          if s[i] == '\0': return r
          r = x.vals[j]
          inc(i)
          break
      break # character not found
    of rnFull:
      var x = PRadixNodeFull(r)
      var y = x.b[s[i]]
      if s[i] ==