pypreprocessor.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. #!/usr/bin/env python
  2. # pypreprocessor.py
  3. __author__ = 'Evan Plaice'
  4. __coauthor__ = 'Hendi O L, Epikem'
  5. __version__ = '0.7.7'
  6. import sys
  7. import os
  8. import traceback
  9. import imp
  10. import io
  11. class preprocessor:
  12. def __init__(self, inFile=sys.argv[1], outFile='', defines=[],
  13. removeMeta=True, escapeChar=None, mode=None, escape='#',
  14. run=False, resume=False, save=True):
  15. # public variables
  16. self.defines = defines
  17. self.input = inFile
  18. self.output = outFile
  19. self.removeMeta = removeMeta
  20. self.escapeChar = escapeChar
  21. self.mode = mode
  22. self.escape = escape
  23. self.run = run
  24. self.resume = resume
  25. self.save = save
  26. self.readEncoding = sys.stdin.encoding
  27. self.writeEncoding = sys.stdout.encoding
  28. # private variables
  29. self.__linenum = 0
  30. self.__excludeblock = False
  31. self.__ifblocks = []
  32. self.__ifconditions = []
  33. self.__evalsquelch = True
  34. self.__outputBuffer = ''
  35. def check_deprecation(self):
  36. def deprecation(message):
  37. import warnings
  38. warnings.simplefilter('always', DeprecationWarning)
  39. warnings.warn(message, DeprecationWarning)
  40. warnings.simplefilter('default', DeprecationWarning)
  41. if self.escapeChar is not None:
  42. deprecation("'pypreprocessor.escapeChar' is deprecated. Use 'escape' instead.")
  43. if self.escape == '#':
  44. self.escape = self.escapeChar
  45. if self.mode is not None:
  46. msg = "'pypreprocessor.mode' is deprecated. Use 'run/resume/save' options instead."
  47. if self.run != True or self.resume != False or self.save != True:
  48. msg += " Ignoring 'pypreprocessor.mode'."
  49. else:
  50. if self.mode.lower() == 'run':
  51. self.run = True
  52. self.resume = False
  53. self.save = False
  54. elif self.mode.lower() == 'pp':
  55. self.run = False
  56. self.resume = False
  57. self.save = True
  58. elif self.mode.lower() == 'ppcont':
  59. self.run = False
  60. self.resume = True
  61. self.save = True
  62. elif self.mode is not None:
  63. print('Unknown mode : ' + str(self.mode))
  64. deprecation(msg)
  65. # reseting internal things to parse a second file
  66. def __reset_internal(self):
  67. self.__linenum = 0
  68. self.__excludeblock = False
  69. self.__ifblocks = []
  70. self.__ifconditions = []
  71. self.__evalsquelch = True
  72. self.__outputBuffer = ''
  73. # the #define directive
  74. def define(self, define):
  75. self.defines.append(define)
  76. # the #undef directive
  77. def undefine(self, define):
  78. # re-map the defines list excluding the define specified in the args
  79. self.defines[:] = [x for x in self.defines if x != define]
  80. # search: if define is defined
  81. def search_defines(self, define):
  82. if define in self.defines:
  83. return True
  84. else:
  85. return False
  86. # returning: validness of #ifdef #else block
  87. def __if(self):
  88. value = bool(self.__ifblocks)
  89. for ib in self.__ifblocks:
  90. value *= ib # * represents and: value = value and ib
  91. return not value # not: because True means removing
  92. # evaluate
  93. def lexer(self, line):
  94. if line == "":
  95. return False, True
  96. # return values are (squelch, metadata)
  97. if not (self.__ifblocks or self.__excludeblock):
  98. if 'pypreprocessor.parse()' in line:
  99. return True, True
  100. # this block only for faster processing (not necessary)
  101. elif line[:len(self.escape)] != self.escape and line[0] != '"':
  102. return False, False
  103. # handle #define directives
  104. if line[:len(self.escape) + 6] == self.escape + 'define':
  105. if len(line.split()) != 2:
  106. self.exit_error(self.escape + 'define')
  107. else:
  108. self.define(line.split()[1])
  109. return False, True
  110. # handle #undef directives
  111. elif line[:len(self.escape) + 5] == self.escape + 'undef':
  112. if len(line.split()) != 2:
  113. self.exit_error(self.escape + 'undef')
  114. else:
  115. self.undefine(line.split()[1])
  116. return False, True
  117. # handle #exclude directives
  118. elif line[:len(self.escape) + 7] == self.escape + 'exclude' or (not self.__excludeblock and line[:3] == '"""'):
  119. if len(line.split()) != 1:
  120. self.exit_error(self.escape + 'exclude')
  121. else:
  122. self.__excludeblock = True
  123. return False, True
  124. # handle #endexclude directives
  125. elif line[:len(self.escape) + 10] == self.escape + 'endexclude' or (self.__excludeblock and line[:3] == '"""'):
  126. if len(line.split()) != 1:
  127. self.exit_error(self.escape + 'endexclude')
  128. else:
  129. self.__excludeblock = False
  130. return False, True
  131. # handle #ifnotdef directives (is the same as: #ifdef X #else)
  132. elif line[:len(self.escape) + 8] == self.escape + 'ifdefnot':
  133. if len(line.split()) != 2:
  134. self.exit_error(self.escape + 'ifdefnot')
  135. else:
  136. self.__ifblocks.append(not self.search_defines(line.split()[1]))
  137. self.__ifconditions.append(line.split()[1])
  138. return False, True
  139. # handle #ifnotdef directives (is the same as: #ifdef X #else)
  140. elif line[:len(self.escape) + 6] == self.escape + 'ifndef':
  141. if len(line.split()) != 2:
  142. self.exit_error(self.escape + 'ifndef')
  143. else:
  144. self.__ifblocks.append(not self.search_defines(line.split()[1]))
  145. self.__ifconditions.append(line.split()[1])
  146. return False, True
  147. # handle #ifdef directives
  148. elif line[:len(self.escape) + 5] == self.escape + 'ifdef':
  149. if len(line.split()) != 2:
  150. self.exit_error(self.escape + 'ifdef')
  151. else:
  152. self.__ifblocks.append(self.search_defines(line.split()[1]))
  153. self.__ifconditions.append(line.split()[1])
  154. return False, True
  155. # handle #else...
  156. # handle #elseif directives
  157. elif line[:len(self.escape) + 6] == self.escape + 'elseif':
  158. if len(line.split()) != 2:
  159. self.exit_error(self.escape + 'elseif')
  160. else:
  161. self.__ifblocks[-1] = not self.__ifblocks[-1]
  162. # self.search_defines(self.__ifconditions[-1]))
  163. self.__ifblocks.append(self.search_defines(line.split()[1]))
  164. self.__ifconditions.append(line.split()[1])
  165. return False, True
  166. # handle #else directives
  167. elif line[:len(self.escape) + 4] == self.escape + 'else':
  168. if len(line.split()) != 1:
  169. self.exit_error(self.escape + 'else')
  170. else:
  171. self.__ifblocks[-1] = not self.__ifblocks[-1]
  172. # self.search_defines(self.__ifconditions[-1]))
  173. return False, True
  174. # handle #endif..
  175. # handle #endififdef
  176. elif line[:len(self.escape) + 10] == self.escape + 'endififdef':
  177. if len(line.split()) != 2:
  178. self.exit_error(self.escape + 'endififdef')
  179. else:
  180. if len(self.__ifconditions) >= 1:
  181. self.__ifblocks.pop(-1)
  182. self.__ifcondition = self.__ifconditions.pop(-1)
  183. else:
  184. self.__ifblocks = []
  185. self.__ifconditions = []
  186. self.__ifblocks.append(self.search_defines(line.split()[1]))
  187. self.__ifconditions.append(line.split()[1])
  188. return False, True
  189. # handle #endifall directives
  190. elif line[:len(self.escape) + 8] == self.escape + 'endifall':
  191. if len(line.split()) != 1:
  192. self.exit_error(self.escape + 'endifall')
  193. else:
  194. self.__ifblocks = []
  195. self.__ifconditions = []
  196. return False, True
  197. # handle #endif and #endif numb directives
  198. elif line[:len(self.escape) + 5] == self.escape + 'endif':
  199. if len(line.split()) != 1:
  200. self.exit_error(self.escape + 'endif number')
  201. else:
  202. try:
  203. number = int(line[6:])
  204. except ValueError as VE:
  205. # print('ValueError',VE)
  206. # self.exit_error(self.escape + 'endif number')
  207. number = 1
  208. if len(self.__ifconditions) > number:
  209. for i in range(0, number):
  210. self.__ifblocks.pop(-1)
  211. self.__ifcondition = self.__ifconditions.pop(-1)
  212. elif len(self.__ifconditions) == number:
  213. self.__ifblocks = []
  214. self.__ifconditions = []
  215. else:
  216. print('Warning try to remove more blocks than present', self.input, self.__linenum)
  217. self.__ifblocks = []
  218. self.__ifconditions = []
  219. return False, True
  220. else: # No directive --> execute
  221. # process the excludeblock
  222. if self.__excludeblock is True:
  223. return True, False
  224. # process the ifblock
  225. elif self.__ifblocks: # is True:
  226. return self.__if(), False
  227. elif line[0] == "#":
  228. return True, True
  229. # here can add other stuff for deleting comnments eg
  230. else:
  231. return False, False
  232. # error handling
  233. def exit_error(self, directive):
  234. print('File: "' + self.input + '", line ' + str(self.__linenum))
  235. print('SyntaxError: Invalid ' + directive + ' directive')
  236. sys.exit(1)
  237. def rewrite_traceback(self):
  238. trace = traceback.format_exc().splitlines()
  239. index = 0
  240. for line in trace:
  241. if index == (len(trace) - 2):
  242. print(line.replace("<string>", self.input))
  243. else:
  244. print(line)
  245. index += 1
  246. # parsing/processing
  247. def parse(self):
  248. self.__reset_internal()
  249. self.check_deprecation()
  250. # open the input file
  251. input_file = io.open(os.path.join(self.input), 'r', encoding=self.readEncoding)
  252. try:
  253. # process the input file
  254. for line in input_file:
  255. self.__linenum += 1
  256. # to squelch or not to squelch
  257. squelch, metaData = self.lexer(line.strip())
  258. # process and output
  259. if self.removeMeta is True:
  260. if metaData is True or squelch is True:
  261. continue
  262. if squelch is True:
  263. if metaData:
  264. self.__outputBuffer += self.escape + line
  265. else:
  266. self.__outputBuffer += self.escape[0] + line
  267. continue
  268. if squelch is False:
  269. self.__outputBuffer += line
  270. continue
  271. finally:
  272. input_file.close()
  273. # Warnings for unclosed #ifdef blocks
  274. if self.__ifblocks:
  275. print('Warning: Number of unclosed Ifdefblocks: ', len(self.__ifblocks))
  276. print('Can cause unwished behaviour in the preprocessed code, preprocessor is safe')
  277. try:
  278. select = input('Do you want more Information? ')
  279. except SyntaxError:
  280. select = 'no'
  281. select = select.lower()
  282. if select in ('yes', 'true', 'y', '1'):
  283. print('Name of input and output file: ', self.input, ' ', self.output)
  284. for i, item in enumerate(self.__ifconditions):
  285. if (item in self.defines) != self.__ifblocks[i]:
  286. cond = ' else '
  287. else:
  288. cond = ' if '
  289. print('Block:', item, ' is in condition: ', cond)
  290. self.post_process()
  291. # post-processor
  292. def post_process(self):
  293. try:
  294. # set file name
  295. if self.output == '':
  296. self.output = self.input[0:-len(self.input.split('.')[-1]) - 1] + '_out.' + self.input.split('.')[-1]
  297. # open file for output
  298. output_file = io.open(self.output, 'w', encoding=self.writeEncoding)
  299. # write post-processed code to file
  300. output_file.write(self.__outputBuffer)
  301. finally:
  302. output_file.close()
  303. if self.run:
  304. # if this module is loaded as a library override the import
  305. if imp.lock_held() is True:
  306. self.override_import()
  307. else:
  308. self.on_the_fly()
  309. if not self.save:
  310. # remove tmp file
  311. if os.path.exists(self.output):
  312. os.remove(self.output)
  313. if not self.resume:
  314. # break execution so python doesn't
  315. # run the rest of the pre-processed code
  316. sys.exit(0)
  317. # postprocessor - override an import
  318. def override_import(self):
  319. try:
  320. moduleName = self.input.split('.')[0]
  321. tmpModuleName = self.output.split('.')[0]
  322. del sys.modules[moduleName]
  323. sys.modules[tmpModuleName] = __import__(tmpModuleName)
  324. sys.modules[moduleName] = __import__(tmpModuleName)
  325. except:
  326. self.rewrite_traceback()
  327. finally:
  328. # remove tmp (.py & .pyc) files
  329. os.remove(self.output)
  330. os.remove(self.output + 'c')
  331. # postprocessor - on-the-fly execution
  332. def on_the_fly(self):
  333. try:
  334. f = io.open(self.output, "r", encoding=self.readEncoding)
  335. exec (f.read())
  336. f.close()
  337. except:
  338. self.rewrite_traceback()
  339. if __name__ == "__main__":
  340. pypreprocessor = preprocessor()
  341. pypreprocessor.parse()