pretty.nim 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements the code "prettifier". This is part of the toolchain
  10. ## to convert Nim code into a consistent style.
  11. import
  12. strutils, os, intsets, strtabs
  13. import compiler/options, compiler/ast, compiler/astalgo, compiler/msgs,
  14. compiler/semdata, compiler/nimfix/prettybase, compiler/ropes, compiler/idents
  15. type
  16. StyleCheck* {.pure.} = enum None, Warn, Auto
  17. var
  18. gOverWrite* = true
  19. gStyleCheck*: StyleCheck
  20. gCheckExtern*, gOnlyMainfile*: bool
  21. proc overwriteFiles*() =
  22. let doStrip = options.getConfigVar("pretty.strip").normalize == "on"
  23. for i in 0 .. high(gSourceFiles):
  24. if gSourceFiles[i].dirty and not gSourceFiles[i].isNimfixFile and
  25. (not gOnlyMainfile or gSourceFiles[i].fileIdx == gProjectMainIdx):
  26. let newFile = if gOverWrite: gSourceFiles[i].fullpath
  27. else: gSourceFiles[i].fullpath.changeFileExt(".pretty.nim")
  28. try:
  29. var f = open(newFile, fmWrite)
  30. for line in gSourceFiles[i].lines:
  31. if doStrip:
  32. f.write line.strip(leading = false, trailing = true)
  33. else:
  34. f.write line
  35. f.write(gSourceFiles[i].newline)
  36. f.close
  37. except IOError:
  38. rawMessage(errCannotOpenFile, newFile)
  39. proc `=~`(s: string, a: openArray[string]): bool =
  40. for x in a:
  41. if s.startsWith(x): return true
  42. proc beautifyName(s: string, k: TSymKind): string =
  43. # minimal set of rules here for transition:
  44. # GC_ is allowed
  45. let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
  46. if allUpper and k in {skConst, skEnumField, skType}: return s
  47. result = newStringOfCap(s.len)
  48. var i = 0
  49. case k
  50. of skType, skGenericParam:
  51. # Types should start with a capital unless builtins like 'int' etc.:
  52. if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
  53. "char", "byte", "bool", "openArray", "seq", "array", "void",
  54. "pointer", "float", "csize", "cdouble", "cchar", "cschar",
  55. "cshort", "cu", "nil", "expr", "stmt", "typedesc", "auto", "any",
  56. "range", "openarray", "varargs", "set", "cfloat"
  57. ]:
  58. result.add s[i]
  59. else:
  60. result.add toUpperAscii(s[i])
  61. of skConst, skEnumField:
  62. # for 'const' we keep how it's spelt; either upper case or lower case:
  63. result.add s[0]
  64. else:
  65. # as a special rule, don't transform 'L' to 'l'
  66. if s.len == 1 and s[0] == 'L': result.add 'L'
  67. elif '_' in s: result.add(s[i])
  68. else: result.add toLowerAscii(s[0])
  69. inc i
  70. while i < s.len:
  71. if s[i] == '_':
  72. if i > 0 and s[i-1] in {'A'..'Z'}:
  73. # don't skip '_' as it's essential for e.g. 'GC_disable'
  74. result.add('_')
  75. inc i
  76. result.add s[i]
  77. else:
  78. inc i
  79. result.add toUpperAscii(s[i])
  80. elif allUpper:
  81. result.add toLowerAscii(s[i])
  82. else:
  83. result.add s[i]
  84. inc i
  85. proc replaceInFile(info: TLineInfo; newName: string) =
  86. loadFile(info)
  87. let line = gSourceFiles[info.fileIndex].lines[info.line-1]
  88. var first = min(info.col.int, line.len)
  89. if first < 0: return
  90. #inc first, skipIgnoreCase(line, "proc ", first)
  91. while first > 0 and line[first-1] in prettybase.Letters: dec first
  92. if first < 0: return
  93. if line[first] == '`': inc first
  94. let last = first+identLen(line, first)-1
  95. if differ(line, first, last, newName):
  96. # last-first+1 != newName.len or
  97. var x = line.substr(0, first-1) & newName & line.substr(last+1)
  98. system.shallowCopy(gSourceFiles[info.fileIndex].lines[info.line-1], x)
  99. gSourceFiles[info.fileIndex].dirty = true
  100. proc checkStyle(info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
  101. let beau = beautifyName(s, k)
  102. if s != beau:
  103. if gStyleCheck == StyleCheck.Auto:
  104. sym.name = getIdent(beau)
  105. replaceInFile(info, beau)
  106. else:
  107. message(info, hintName, beau)
  108. proc styleCheckDefImpl(info: TLineInfo; s: PSym; k: TSymKind) =
  109. # operators stay as they are:
  110. if k in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters: return
  111. if k in {skType, skGenericParam} and sfAnon in s.flags: return
  112. if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern:
  113. checkStyle(info, s.name.s, k, s)
  114. template styleCheckDef*(info: TLineInfo; s: PSym; k: TSymKind) =
  115. when defined(nimfix):
  116. if gStyleCheck != StyleCheck.None: styleCheckDefImpl(info, s, k)
  117. template styleCheckDef*(info: TLineInfo; s: PSym) =
  118. styleCheckDef(info, s, s.kind)
  119. template styleCheckDef*(s: PSym) =
  120. styleCheckDef(s.info, s, s.kind)
  121. proc styleCheckUseImpl(info: TLineInfo; s: PSym) =
  122. if info.fileIndex < 0: return
  123. # we simply convert it to what it looks like in the definition
  124. # for consistency
  125. # operators stay as they are:
  126. if s.kind in {skResult, skTemp} or s.name.s[0] notin prettybase.Letters:
  127. return
  128. if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
  129. let newName = s.name.s
  130. replaceInFile(info, newName)
  131. #if newName == "File": writeStackTrace()
  132. template styleCheckUse*(info: TLineInfo; s: PSym) =
  133. when defined(nimfix):
  134. if gStyleCheck != StyleCheck.None: styleCheckUseImpl(info, s)