mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			168 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
	
		
			5.9 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
r"""Utilities to compile possibly incomplete Python source code.
 | 
						|
 | 
						|
This module provides two interfaces, broadly similar to the builtin
 | 
						|
function compile(), which take program text, a filename and a 'mode'
 | 
						|
and:
 | 
						|
 | 
						|
- Return code object if the command is complete and valid
 | 
						|
- Return None if the command is incomplete
 | 
						|
- Raise SyntaxError, ValueError or OverflowError if the command is a
 | 
						|
  syntax error (OverflowError and ValueError can be produced by
 | 
						|
  malformed literals).
 | 
						|
 | 
						|
Approach:
 | 
						|
 | 
						|
First, check if the source consists entirely of blank lines and
 | 
						|
comments; if so, replace it with 'pass', because the built-in
 | 
						|
parser doesn't always do the right thing for these.
 | 
						|
 | 
						|
Compile three times: as is, with \n, and with \n\n appended.  If it
 | 
						|
compiles as is, it's complete.  If it compiles with one \n appended,
 | 
						|
we expect more.  If it doesn't compile either way, we compare the
 | 
						|
error we get when compiling with \n or \n\n appended.  If the errors
 | 
						|
are the same, the code is broken.  But if the errors are different, we
 | 
						|
expect more.  Not intuitive; not even guaranteed to hold in future
 | 
						|
releases; but this matches the compiler's behavior from Python 1.4
 | 
						|
through 2.2, at least.
 | 
						|
 | 
						|
Caveat:
 | 
						|
 | 
						|
It is possible (but not likely) that the parser stops parsing with a
 | 
						|
successful outcome before reaching the end of the source; in this
 | 
						|
case, trailing symbols may be ignored instead of causing an error.
 | 
						|
For example, a backslash followed by two newlines may be followed by
 | 
						|
arbitrary garbage.  This will be fixed once the API for the parser is
 | 
						|
better.
 | 
						|
 | 
						|
The two interfaces are:
 | 
						|
 | 
						|
compile_command(source, filename, symbol):
 | 
						|
 | 
						|
    Compiles a single command in the manner described above.
 | 
						|
 | 
						|
CommandCompiler():
 | 
						|
 | 
						|
    Instances of this class have __call__ methods identical in
 | 
						|
    signature to compile_command; the difference is that if the
 | 
						|
    instance compiles program text containing a __future__ statement,
 | 
						|
    the instance 'remembers' and compiles all subsequent program texts
 | 
						|
    with the statement in force.
 | 
						|
 | 
						|
The module also provides another class:
 | 
						|
 | 
						|
Compile():
 | 
						|
 | 
						|
    Instances of this class act like the built-in function compile,
 | 
						|
    but with 'memory' in the sense described above.
 | 
						|
"""
 | 
						|
 | 
						|
import __future__
 | 
						|
 | 
						|
_features = [getattr(__future__, fname)
 | 
						|
             for fname in __future__.all_feature_names]
 | 
						|
 | 
						|
__all__ = ["compile_command", "Compile", "CommandCompiler"]
 | 
						|
 | 
						|
PyCF_DONT_IMPLY_DEDENT = 0x200          # Matches pythonrun.h
 | 
						|
 | 
						|
def _maybe_compile(compiler, source, filename, symbol):
 | 
						|
    # Check for source consisting of only blank lines and comments
 | 
						|
    for line in source.split("\n"):
 | 
						|
        line = line.strip()
 | 
						|
        if line and line[0] != '#':
 | 
						|
            break               # Leave it alone
 | 
						|
    else:
 | 
						|
        if symbol != "eval":
 | 
						|
            source = "pass"     # Replace it with a 'pass' statement
 | 
						|
 | 
						|
    err = err1 = err2 = None
 | 
						|
    code = code1 = code2 = None
 | 
						|
 | 
						|
    try:
 | 
						|
        code = compiler(source, filename, symbol)
 | 
						|
    except SyntaxError, err:
 | 
						|
        pass
 | 
						|
 | 
						|
    try:
 | 
						|
        code1 = compiler(source + "\n", filename, symbol)
 | 
						|
    except SyntaxError, err1:
 | 
						|
        pass
 | 
						|
 | 
						|
    try:
 | 
						|
        code2 = compiler(source + "\n\n", filename, symbol)
 | 
						|
    except SyntaxError, err2:
 | 
						|
        pass
 | 
						|
 | 
						|
    if code:
 | 
						|
        return code
 | 
						|
    if not code1 and repr(err1) == repr(err2):
 | 
						|
        raise SyntaxError, err1
 | 
						|
 | 
						|
def _compile(source, filename, symbol):
 | 
						|
    return compile(source, filename, symbol, PyCF_DONT_IMPLY_DEDENT)
 | 
						|
 | 
						|
def compile_command(source, filename="<input>", symbol="single"):
 | 
						|
    r"""Compile a command and determine whether it is incomplete.
 | 
						|
 | 
						|
    Arguments:
 | 
						|
 | 
						|
    source -- the source string; may contain \n characters
 | 
						|
    filename -- optional filename from which source was read; default
 | 
						|
                "<input>"
 | 
						|
    symbol -- optional grammar start symbol; "single" (default) or "eval"
 | 
						|
 | 
						|
    Return value / exceptions raised:
 | 
						|
 | 
						|
    - Return a code object if the command is complete and valid
 | 
						|
    - Return None if the command is incomplete
 | 
						|
    - Raise SyntaxError, ValueError or OverflowError if the command is a
 | 
						|
      syntax error (OverflowError and ValueError can be produced by
 | 
						|
      malformed literals).
 | 
						|
    """
 | 
						|
    return _maybe_compile(_compile, source, filename, symbol)
 | 
						|
 | 
						|
class Compile:
 | 
						|
    """Instances of this class behave much like the built-in compile
 | 
						|
    function, but if one is used to compile text containing a future
 | 
						|
    statement, it "remembers" and compiles all subsequent program texts
 | 
						|
    with the statement in force."""
 | 
						|
    def __init__(self):
 | 
						|
        self.flags = PyCF_DONT_IMPLY_DEDENT
 | 
						|
 | 
						|
    def __call__(self, source, filename, symbol):
 | 
						|
        codeob = compile(source, filename, symbol, self.flags, 1)
 | 
						|
        for feature in _features:
 | 
						|
            if codeob.co_flags & feature.compiler_flag:
 | 
						|
                self.flags |= feature.compiler_flag
 | 
						|
        return codeob
 | 
						|
 | 
						|
class CommandCompiler:
 | 
						|
    """Instances of this class have __call__ methods identical in
 | 
						|
    signature to compile_command; the difference is that if the
 | 
						|
    instance compiles program text containing a __future__ statement,
 | 
						|
    the instance 'remembers' and compiles all subsequent program texts
 | 
						|
    with the statement in force."""
 | 
						|
 | 
						|
    def __init__(self,):
 | 
						|
        self.compiler = Compile()
 | 
						|
 | 
						|
    def __call__(self, source, filename="<input>", symbol="single"):
 | 
						|
        r"""Compile a command and determine whether it is incomplete.
 | 
						|
 | 
						|
        Arguments:
 | 
						|
 | 
						|
        source -- the source string; may contain \n characters
 | 
						|
        filename -- optional filename from which source was read;
 | 
						|
                    default "<input>"
 | 
						|
        symbol -- optional grammar start symbol; "single" (default) or
 | 
						|
                  "eval"
 | 
						|
 | 
						|
        Return value / exceptions raised:
 | 
						|
 | 
						|
        - Return a code object if the command is complete and valid
 | 
						|
        - Return None if the command is incomplete
 | 
						|
        - Raise SyntaxError, ValueError or OverflowError if the command is a
 | 
						|
          syntax error (OverflowError and ValueError can be produced by
 | 
						|
          malformed literals).
 | 
						|
        """
 | 
						|
        return _maybe_compile(self.compiler, source, filename, symbol)
 |