driver.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. # Copyright 2004-2005 Elemental Security, Inc. All Rights Reserved.
  2. # Licensed to PSF under a Contributor Agreement.
  3. # Modifications:
  4. # Copyright 2006 Google, Inc. All Rights Reserved.
  5. # Licensed to PSF under a Contributor Agreement.
  6. """Parser driver.
  7. This provides a high-level interface to parse a file into a syntax tree.
  8. """
  9. __author__ = "Guido van Rossum <guido@python.org>"
  10. __all__ = ["Driver", "load_grammar"]
  11. # Python imports
  12. import io
  13. import os
  14. import logging
  15. import pkgutil
  16. import sys
  17. # Pgen imports
  18. from . import grammar, parse, token, tokenize, pgen
  19. class Driver(object):
  20. def __init__(self, grammar, convert=None, logger=None):
  21. self.grammar = grammar
  22. if logger is None:
  23. logger = logging.getLogger()
  24. self.logger = logger
  25. self.convert = convert
  26. def parse_tokens(self, tokens, debug=False):
  27. """Parse a series of tokens and return the syntax tree."""
  28. # XXX Move the prefix computation into a wrapper around tokenize.
  29. p = parse.Parser(self.grammar, self.convert)
  30. p.setup()
  31. lineno = 1
  32. column = 0
  33. type = value = start = end = line_text = None
  34. prefix = ""
  35. for quintuple in tokens:
  36. type, value, start, end, line_text = quintuple
  37. if start != (lineno, column):
  38. assert (lineno, column) <= start, ((lineno, column), start)
  39. s_lineno, s_column = start
  40. if lineno < s_lineno:
  41. prefix += "\n" * (s_lineno - lineno)
  42. lineno = s_lineno
  43. column = 0
  44. if column < s_column:
  45. prefix += line_text[column:s_column]
  46. column = s_column
  47. if type in (tokenize.COMMENT, tokenize.NL):
  48. prefix += value
  49. lineno, column = end
  50. if value.endswith("\n"):
  51. lineno += 1
  52. column = 0
  53. continue
  54. if type == token.OP:
  55. type = grammar.opmap[value]
  56. if debug:
  57. self.logger.debug("%s %r (prefix=%r)",
  58. token.tok_name[type], value, prefix)
  59. if p.addtoken(type, value, (prefix, start)):
  60. if debug:
  61. self.logger.debug("Stop.")
  62. break
  63. prefix = ""
  64. lineno, column = end
  65. if value.endswith("\n"):
  66. lineno += 1
  67. column = 0
  68. else:
  69. # We never broke out -- EOF is too soon (how can this happen???)
  70. raise parse.ParseError("incomplete input",
  71. type, value, (prefix, start))
  72. return p.rootnode
  73. def parse_stream_raw(self, stream, debug=False):
  74. """Parse a stream and return the syntax tree."""
  75. tokens = tokenize.generate_tokens(stream.readline)
  76. return self.parse_tokens(tokens, debug)
  77. def parse_stream(self, stream, debug=False):
  78. """Parse a stream and return the syntax tree."""
  79. return self.parse_stream_raw(stream, debug)
  80. def parse_file(self, filename, encoding=None, debug=False):
  81. """Parse a file and return the syntax tree."""
  82. with io.open(filename, "r", encoding=encoding) as stream:
  83. return self.parse_stream(stream, debug)
  84. def parse_string(self, text, debug=False):
  85. """Parse a string and return the syntax tree."""
  86. tokens = tokenize.generate_tokens(io.StringIO(text).readline)
  87. return self.parse_tokens(tokens, debug)
  88. def _generate_pickle_name(gt):
  89. head, tail = os.path.splitext(gt)
  90. if tail == ".txt":
  91. tail = ""
  92. return head + tail + ".".join(map(str, sys.version_info)) + ".pickle"
  93. def load_grammar(gt="Grammar.txt", gp=None,
  94. save=True, force=False, logger=None):
  95. """Load the grammar (maybe from a pickle)."""
  96. if logger is None:
  97. logger = logging.getLogger()
  98. gp = _generate_pickle_name(gt) if gp is None else gp
  99. if force or not _newer(gp, gt):
  100. logger.info("Generating grammar tables from %s", gt)
  101. g = pgen.generate_grammar(gt)
  102. if save:
  103. logger.info("Writing grammar tables to %s", gp)
  104. try:
  105. g.dump(gp)
  106. except OSError as e:
  107. logger.info("Writing failed: %s", e)
  108. else:
  109. g = grammar.Grammar()
  110. g.load(gp)
  111. return g
  112. def _newer(a, b):
  113. """Inquire whether file a was written since file b."""
  114. if not os.path.exists(a):
  115. return False
  116. if not os.path.exists(b):
  117. return True
  118. return os.path.getmtime(a) >= os.path.getmtime(b)
  119. def load_packaged_grammar(package, grammar_source):
  120. """Normally, loads a pickled grammar by doing
  121. pkgutil.get_data(package, pickled_grammar)
  122. where *pickled_grammar* is computed from *grammar_source* by adding the
  123. Python version and using a ``.pickle`` extension.
  124. However, if *grammar_source* is an extant file, load_grammar(grammar_source)
  125. is called instead. This facilitates using a packaged grammar file when needed
  126. but preserves load_grammar's automatic regeneration behavior when possible.
  127. """
  128. if os.path.isfile(grammar_source):
  129. return load_grammar(grammar_source)
  130. pickled_name = _generate_pickle_name(os.path.basename(grammar_source))
  131. data = pkgutil.get_data(package, pickled_name)
  132. g = grammar.Grammar()
  133. g.loads(data)
  134. return g
  135. def main(*args):
  136. """Main program, when run as a script: produce grammar pickle files.
  137. Calls load_grammar for each argument, a path to a grammar text file.
  138. """
  139. if not args:
  140. args = sys.argv[1:]
  141. logging.basicConfig(level=logging.INFO, stream=sys.stdout,
  142. format='%(message)s')
  143. for gt in args:
  144. load_grammar(gt, save=True, force=True)
  145. return True
  146. if __name__ == "__main__":
  147. sys.exit(int(not main()))