summary refs log tree commit diff stats
path: root/compiler/nimfix
diff options
context:
space:
mode:
authorAraq <rumpf_a@web.de>2014-10-05 23:36:47 +0200
committerAraq <rumpf_a@web.de>2014-10-05 23:36:47 +0200
commitda0fab70467a138081f5e0240fa66c7cae460b9e (patch)
treeaa7263454b320e0c653c51ab46f74c89d0defe43 /compiler/nimfix
parent3354f7e0982d1db512cd2aad9cd78a52d3379d02 (diff)
downloadNim-da0fab70467a138081f5e0240fa66c7cae460b9e.tar.gz
nimfix now in its own directory; single file mode now the default
Diffstat (limited to 'compiler/nimfix')
-rw-r--r--compiler/nimfix/nimfix.nim106
-rw-r--r--compiler/nimfix/nimfix.nim.cfg17
-rw-r--r--compiler/nimfix/pretty.nim152
-rw-r--r--compiler/nimfix/prettybase.nim93
4 files changed, 368 insertions, 0 deletions
diff --git a/compiler/nimfix/nimfix.nim b/compiler/nimfix/nimfix.nim
new file mode 100644
index 000000000..e561b6d4d
--- /dev/null
+++ b/compiler/nimfix/nimfix.nim
@@ -0,0 +1,106 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2014 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## Nimfix is a tool that helps to convert old-style Nimrod code to Nim code.
+
+import strutils, os, parseopt
+import options, commands, modules, sem, passes, passaux, pretty, msgs, nimconf,
+  extccomp, condsyms, lists
+
+const Usage = """
+Nimfix - Tool to patch Nim code
+Usage:
+  nimfix [options] projectflie.nim
+
+Options:
+  --overwriteFiles:on|off          overwrite the original nim files.
+                                   DEFAULT is ON!
+  --wholeProject                   overwrite every processed file.
+  --checkExtern:on|off             style check also extern names
+  --styleCheck:on|off|auto         performs style checking for identifiers
+                                   and suggests an alternative spelling; 
+                                   'auto' corrects the spelling.
+
+In addition, all command line options of Nim are supported.
+"""
+
+proc mainCommand =
+  #msgs.gErrorMax = high(int)  # do not stop after first error
+  registerPass verbosePass
+  registerPass semPass
+  gCmd = cmdPretty
+  appendStr(searchPaths, options.libpath)
+  if gProjectFull.len != 0:
+    # current path is always looked first for modules
+    prependStr(searchPaths, gProjectPath)
+
+  compileProject()
+  pretty.overwriteFiles()
+
+proc processCmdLine*(pass: TCmdLinePass, cmd: string) =
+  var p = parseopt.initOptParser(cmd)
+  var argsCount = 0
+  gOnlyMainfile = true
+  while true: 
+    parseopt.next(p)
+    case p.kind
+    of cmdEnd: break 
+    of cmdLongoption, cmdShortOption: 
+      case p.key.normalize
+      of "overwritefiles":
+        case p.val.normalize
+        of "on": gOverWrite = true
+        of "off": gOverWrite = false
+        else: localError(gCmdLineInfo, errOnOrOffExpected)
+      of "checkextern":
+        case p.val.normalize
+        of "on": gCheckExtern = true
+        of "off": gCheckExtern = false
+        else: localError(gCmdLineInfo, errOnOrOffExpected)
+      of "stylecheck": 
+        case p.val.normalize
+        of "off": gStyleCheck = StyleCheck.None
+        of "on": gStyleCheck = StyleCheck.Warn
+        of "auto": gStyleCheck = StyleCheck.Auto
+        else: localError(gCmdLineInfo, errOnOrOffExpected)
+      of "wholeproject": gOnlyMainfile = false
+      else:
+        processSwitch(pass, p)
+    of cmdArgument:
+      options.gProjectName = unixToNativePath(p.key)
+      # if processArgument(pass, p, argsCount): break
+
+proc handleCmdLine() =
+  if paramCount() == 0:
+    stdout.writeln(Usage)
+  else:
+    processCmdLine(passCmd1, "")
+    if gProjectName != "":
+      try:
+        gProjectFull = canonicalizePath(gProjectName)
+      except OSError:
+        gProjectFull = gProjectName
+      var p = splitFile(gProjectFull)
+      gProjectPath = p.dir
+      gProjectName = p.name
+    else:
+      gProjectPath = getCurrentDir()
+    loadConfigs(DefaultConfig) # load all config files
+    # now process command line arguments again, because some options in the
+    # command line can overwite the config file's settings
+    extccomp.initVars()
+    processCmdLine(passCmd2, "")
+    mainCommand()
+
+when compileOption("gc", "v2") or compileOption("gc", "refc"):
+  GC_disableMarkAndSweep()
+
+condsyms.initDefines()
+defineSymbol "nimfix"
+handleCmdline()
diff --git a/compiler/nimfix/nimfix.nim.cfg b/compiler/nimfix/nimfix.nim.cfg
new file mode 100644
index 000000000..31a41e080
--- /dev/null
+++ b/compiler/nimfix/nimfix.nim.cfg
@@ -0,0 +1,17 @@
+# Special configuration file for the Nim project
+# gc:markAndSweep
+
+hint[XDeclaredButNotUsed]:off
+path:"$projectPath/../.."
+
+path:"$lib/packages/docutils"
+path:"$nim/compiler"
+
+define:useStdoutAsStdmsg
+symbol:nimfix
+define:nimfix
+
+cs:partial
+#define:useNodeIds
+define:booting
+define:noDocgen
diff --git a/compiler/nimfix/pretty.nim b/compiler/nimfix/pretty.nim
new file mode 100644
index 000000000..acac574af
--- /dev/null
+++ b/compiler/nimfix/pretty.nim
@@ -0,0 +1,152 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2014 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 Nim code into a consistent style.
+
+import 
+  strutils, os, options, ast, astalgo, msgs, ropes, idents,
+  intsets, strtabs, semdata, prettybase
+
+type
+  StyleCheck* {.pure.} = enum None, Warn, Auto
+
+var
+  gOverWrite* = true
+  gStyleCheck*: StyleCheck
+  gCheckExtern*, gOnlyMainfile*: bool
+
+proc overwriteFiles*() =
+  let doStrip = options.getConfigVar("pretty.strip").normalize == "on"
+  for i in 0 .. high(gSourceFiles):
+    if gSourceFiles[i].dirty and not gSourceFiles[i].isNimfixFile and
+        (not gOnlyMainfile or gSourceFiles[i].fileIdx == gProjectMainIdx):
+      let newFile = if gOverWrite: gSourceFiles[i].fullpath
+                    else: gSourceFiles[i].fullpath.changeFileExt(".pretty.nim")
+      try:
+        var f = open(newFile, fmWrite)
+        for line in gSourceFiles[i].lines:
+          if doStrip:
+            f.write line.strip(leading = false, trailing = true)
+          else:
+            f.write line
+          f.write(gSourceFiles[i].newline)
+        f.close
+      except IOError:
+        rawMessage(errCannotOpenFile, newFile)
+
+proc `=~`(s: string, a: openArray[string]): bool =
+  for x in a:
+    if s.startsWith(x): return true
+
+proc beautifyName(s: string, k: TSymKind): string =
+  # minimal set of rules here for transition:
+  # GC_ is allowed
+
+  let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
+  if allUpper and k in {skConst, skEnumField, skType}: return s
+  result = newStringOfCap(s.len)
+  var i = 0
+  case k
+  of skType, skGenericParam:
+    # Types should start with a capital unless builtins like 'int' etc.:
+    if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
+             "char", "byte", "bool", "openArray", "seq", "array", "void",
+             "pointer", "float", "csize", "cdouble", "cchar", "cschar",
+             "cshort", "cu", "nil", "expr", "stmt", "typedesc", "auto", "any",
+             "range", "openarray", "varargs", "set", "cfloat"
+             ]:
+      result.add s[i]
+    else:
+      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'
+    elif '_' in s: result.add(s[i])
+    else: result.add toLower(s[0])
+  inc i
+  while i < s.len:
+    if s[i] == '_':
+      if i > 0 and s[i-1] in {'A'..'Z'}:
+        # don't skip '_' as it's essential for e.g. 'GC_disable'
+        result.add('_')
+        inc i
+        result.add s[i]
+      else:
+        inc i
+        result.add toUpper(s[i])
+    elif allUpper:
+      result.add toLower(s[i])
+    else:
+      result.add s[i]
+    inc i
+
+proc replaceInFile(info: TLineInfo; newName: string) =
+  loadFile(info)
+  
+  let line = gSourceFiles[info.fileIndex].lines[info.line-1]
+  var first = min(info.col.int, line.len)
+  if first < 0: return
+  #inc first, skipIgnoreCase(line, "proc ", first)
+  while first > 0 and line[first-1] in prettybase.Letters: dec first
+  if first < 0: return
+  if line[first] == '`': inc first
+  
+  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)    
+    system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
+    gSourceFiles[info.fileIndex].dirty = true
+
+proc checkStyle(info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
+  let beau = beautifyName(s, k)
+  if s != beau:
+    if gStyleCheck == StyleCheck.Auto: 
+      sym.name = getIdent(beau)
+      replaceInFile(info, beau)
+    else:
+      message(info, hintName, beau)
+
+proc styleCheckDefImpl(info: TLineInfo; s: PSym; k: TSymKind) =
+  # operators stay as they are:
+  if k in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters: return
+  if k in {skType, skGenericParam} and sfAnon in s.flags: return
+  if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern:
+    checkStyle(info, s.name.s, k, s)
+
+template styleCheckDef*(info: TLineInfo; s: PSym; k: TSymKind) =
+  when defined(nimfix):
+    if gStyleCheck != StyleCheck.None: styleCheckDefImpl(info, s, k)
+
+template styleCheckDef*(info: TLineInfo; s: PSym) =
+  styleCheckDef(info, s, s.kind)
+template styleCheckDef*(s: PSym) =
+  styleCheckDef(s.info, s, s.kind)
+
+proc styleCheckUseImpl(info: TLineInfo; s: PSym) =
+  if info.fileIndex < 0: return
+  # we simply convert it to what it looks like in the definition
+  # for consistency
+  
+  # operators stay as they are:
+  if s.kind in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters:
+    return
+  if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
+  let newName = s.name.s
+
+  replaceInFile(info, newName)
+  #if newName == "File": writeStackTrace()
+
+template styleCheckUse*(info: TLineInfo; s: PSym) =
+  when defined(nimfix):
+    if gStyleCheck != StyleCheck.None: styleCheckUseImpl(info, s)
diff --git a/compiler/nimfix/prettybase.nim b/compiler/nimfix/prettybase.nim
new file mode 100644
index 000000000..225b78479
--- /dev/null
+++ b/compiler/nimfix/prettybase.nim
@@ -0,0 +1,93 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2014 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+import ast, msgs, strutils, idents, lexbase, streams
+from os import splitFile
+
+type
+  TSourceFile* = object
+    lines*: seq[string]
+    dirty*, isNimfixFile*: bool
+    fullpath*, newline*: string
+    fileIdx*: int32
+
+var
+  gSourceFiles*: seq[TSourceFile] = @[]
+
+proc loadFile*(info: TLineInfo) =
+  let i = info.fileIndex
+  if i >= gSourceFiles.len:
+    gSourceFiles.setLen(i+1)
+  if gSourceFiles[i].lines.isNil:
+    gSourceFiles[i].fileIdx = info.fileIndex
+    gSourceFiles[i].lines = @[]
+    let path = info.toFullPath
+    gSourceFiles[i].fullpath = path
+    gSourceFiles[i].isNimfixFile = path.splitFile.ext == ".nimfix"
+    # we want to die here for IOError:
+    for line in lines(path):
+      gSourceFiles[i].lines.add(line)
+    # extract line ending of the file:
+    var lex: BaseLexer
+    open(lex, newFileStream(path, fmRead))
+    var pos = lex.bufpos
+    while true:
+      case lex.buf[pos]
+      of '\c': 
+        gSourceFiles[i].newline = "\c\L"
+        break
+      of '\L', '\0':
+        gSourceFiles[i].newline = "\L"
+        break
+      else: discard
+      inc pos
+    close(lex)
+
+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
+
+proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PIdent) =
+  loadFile(info)
+
+  let line = gSourceFiles[info.fileIndex].lines[info.line-1]
+  var first = min(info.col.int, line.len)
+  if first < 0: return
+  #inc first, skipIgnoreCase(line, "proc ", first)
+  while first > 0 and line[first-1] in Letters: dec first
+  if first < 0: return
+  if line[first] == '`': inc first
+  
+  let last = first+identLen(line, first)-1
+  if cmpIgnoreStyle(line[first..last], oldSym.s) == 0:
+    var x = line.substr(0, first-1) & newSym.s & line.substr(last+1)
+    system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
+    gSourceFiles[info.fileIndex].dirty = true
+    #if newSym.s == "File": writeStackTrace()
+
+proc replaceDeprecated*(info: TLineInfo; oldSym, newSym: PSym) =
+  replaceDeprecated(info, oldSym.name, newSym.name)
+
+proc replaceComment*(info: TLineInfo) =
+  loadFile(info)
+
+  let line = gSourceFiles[info.fileIndex].lines[info.line-1]
+  var first = info.col.int
+  if line[first] != '#': inc first
+
+  var x = line.substr(0, first-1) & "discard " & line.substr(first+1).escape
+  system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
+  gSourceFiles[info.fileIndex].dirty = true