summary refs log tree commit diff stats
path: root/compiler/pretty.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/pretty.nim')
-rw-r--r--compiler/pretty.nim181
1 files changed, 181 insertions, 0 deletions
diff --git a/compiler/pretty.nim b/compiler/pretty.nim
new file mode 100644
index 000000000..4545e1c55
--- /dev/null
+++ b/compiler/pretty.nim
@@ -0,0 +1,181 @@
+#
+#
+#           The Nimrod Compiler
+#        (c) Copyright 2013 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements the code "prettifier". This is part of the toolchain
+## to convert Nimrod code into a consistent style.
+
+import 
+  strutils, os, options, ast, astalgo, msgs, ropes, idents, passes,
+  intsets, strtabs
+  
+const
+  removeTP = false # when true, "nimrod pretty" converts TTyp to Typ.
+
+type 
+  TGen = object of TPassContext
+    module*: PSym
+  PGen = ref TGen
+  
+  TSourceFile = object
+    lines: seq[string]
+    dirty: bool
+    fullpath: string
+
+var
+  gSourceFiles: seq[TSourceFile] = @[]
+  rules: PStringTable
+
+proc loadFile(info: TLineInfo) =
+  let i = info.fileIndex
+  if i >= gSourceFiles.len:
+    gSourceFiles.setLen(i+1)
+  if gSourceFiles[i].lines.isNil:
+    gSourceFiles[i].lines = @[]
+    let path = info.toFullPath
+    gSourceFiles[i].fullpath = path
+    # we want to die here for EIO:
+    for line in lines(path):
+      gSourceFiles[i].lines.add(line)
+
+proc overwriteFiles*() =
+  for i in 0 .. high(gSourceFiles):
+    if not gSourceFiles[i].dirty: continue
+    let newFile = gSourceFiles[i].fullpath.changeFileExt(".pretty.nim")
+    try:
+      var f = open(newFile, fmWrite)
+      for line in gSourceFiles[i].lines:
+        f.writeln(line)
+      f.close
+    except EIO:
+      rawMessage(errCannotOpenFile, newFile)
+
+proc beautifyName(s: string, k: TSymKind): string =
+  result = newStringOfCap(s.len)
+  var i = 0
+  case k
+  of skType, skGenericParam:
+    # skip leading 'T'
+    when removeTP:
+      if s[0] == 'T' and s[1] in {'A'..'Z'}:
+        i = 1
+    result.add toUpper(s[i])
+  of skConst, skEnumField:
+    # for 'const' we keep how it's spelt; either upper case or lower case:
+    result.add s[0]
+  else:
+    # as a special rule, don't transform 'L' to 'l'
+    if s.len == 1 and s[0] == 'L': result.add 'L'
+    else: result.add toLower(s[0])
+  inc i
+  let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
+  while i < s.len:
+    if s[i] == '_':
+      inc i
+      result.add toUpper(s[i])
+    elif allUpper:
+      result.add toLower(s[i])
+    else:
+      result.add s[i]
+    inc i
+
+proc checkStyle*(info: TLineInfo, s: string, k: TSymKind) =
+  let beau = beautifyName(s, k)
+  if s != beau:
+    Message(info, errGenerated, 
+      "name does not adhere to naming convention; should be: " & beau)
+
+const
+  Letters = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
+
+proc identLen(line: string, start: int): int =
+  while start+result < line.len and line[start+result] in Letters:
+    inc result
+
+proc differ(line: string, a, b: int, x: string): bool =
+  let y = line[a..b]
+  result = cmpIgnoreStyle(y, x) == 0 and y != x
+  when false:
+    var j = 0
+    for i in a..b:
+      if line[i] != x[j]: return true
+      inc j
+    return false
+
+var cannotRename = initIntSet()
+
+proc processSym(c: PPassContext, n: PNode): PNode = 
+  result = n
+  var g = PGen(c)
+  case n.kind
+  of nkSym:
+    if n.info.fileIndex < 0: return
+    let s = n.sym
+    # operators stay as they are:
+    if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters: return
+    if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
+    
+    if s.id in cannotRename: return
+    
+    let newName = if rules.hasKey(s.name.s): rules[s.name.s]
+                  else: beautifyName(s.name.s, n.sym.kind)
+    
+    loadFile(n.info)
+    
+    let line = gSourceFiles[n.info.fileIndex].lines[n.info.line-1]
+    var first = n.info.col.int
+    if first < 0: return
+    #inc first, skipIgnoreCase(line, "proc ", first)
+    while first > 0 and line[first-1] in Letters: dec first
+    if line[first] == '`': inc first
+    
+    if {sfImportc, sfExportc} * s.flags != {}:
+      # careful, we must ensure the resulting name still matches the external
+      # name:
+      if newName != s.name.s and newName != s.loc.r.ropeToStr and
+          lfFullExternalName notin s.loc.flags:
+        Message(n.info, errGenerated, 
+          "cannot rename $# to $# due to external name" % [s.name.s, newName])
+        cannotRename.incl(s.id)
+        return
+    let last = first+identLen(line, first)-1
+    if differ(line, first, last, newName):
+      # last-first+1 != newName.len or 
+      var x = line.subStr(0, first-1) & newName & line.substr(last+1)
+      when removeTP:
+        # the WinAPI module is full of 'TX = X' which after the substitution
+        # becomes 'X = X'. We remove those lines:
+        if x.match(peg"\s* {\ident} \s* '=' \s* y$1 ('#' .*)?"):
+          x = ""
+      
+      system.shallowCopy(gSourceFiles[n.info.fileIndex].lines[n.info.line-1], x)
+      gSourceFiles[n.info.fileIndex].dirty = true
+  else:
+    for i in 0 .. <n.safeLen:
+      discard processSym(c, n.sons[i])
+
+proc myOpen(module: PSym): PPassContext =
+  var g: PGen
+  new(g)
+  g.module = module
+  result = g
+  if rules.isNil:
+    rules = newStringTable(modeStyleInsensitive)
+    when removeTP:
+      # XXX activate when the T/P stuff is deprecated
+      let path = joinPath([getPrefixDir(), "config", "rename.rules.cfg"])
+      for line in lines(path):
+        if line.len > 0:
+          let colon = line.find(':')
+          if colon > 0:
+            rules[line.substr(0, colon-1)] = line.substr(colon+1)
+          else:
+            rules[line] = line
+
+const prettyPass* = makePass(open = myOpen, process = processSym)
+