File: pycompiler.py

package info (click to toggle)
pypy3 7.0.0%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 111,848 kB
  • sloc: python: 1,291,746; ansic: 74,281; asm: 5,187; cpp: 3,017; sh: 2,533; makefile: 544; xml: 243; lisp: 45; csh: 21; awk: 4
file content (169 lines) | stat: -rw-r--r-- 6,636 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""
General classes for bytecode compilers.
Compiler instances are stored into 'space.getexecutioncontext().compiler'.
"""

from pypy.interpreter import pycode
from pypy.interpreter.pyparser import future, pyparse, error as parseerror
from pypy.interpreter.astcompiler import (astbuilder, codegen, consts, misc,
                                          optimize, ast, validate)
from pypy.interpreter.error import OperationError, oefmt


class AbstractCompiler(object):
    """Abstract base class for a bytecode compiler."""

    # The idea is to grow more methods here over the time,
    # e.g. to handle .pyc files in various ways if we have multiple compilers.

    def __init__(self, space):
        self.space = space
        self.w_compile_hook = space.w_None

    def compile(self, source, filename, mode, flags):
        """Compile and return an pypy.interpreter.eval.Code instance."""
        raise NotImplementedError

    def getcodeflags(self, code):
        """Return the __future__ compiler flags that were used to compile
        the given code object."""
        return 0

    def compile_command(self, source, filename, mode, flags):
        """Same as compile(), but tries to compile a possibly partial
        interactive input.  If more input is needed, it returns None.
        """
        # Hackish default implementation based on the stdlib 'codeop' module.
        # See comments over there.
        space = self.space
        flags |= consts.PyCF_DONT_IMPLY_DEDENT
        # Check for source consisting of only blank lines and comments
        if mode != "eval":
            in_comment = False
            for c in source:
                if c in ' \t\f\v':   # spaces
                    pass
                elif c == '#':
                    in_comment = True
                elif c in '\n\r':
                    in_comment = False
                elif not in_comment:
                    break    # non-whitespace, non-comment character
            else:
                source = "pass"     # Replace it with a 'pass' statement

        try:
            code = self.compile(source, filename, mode, flags)
            return code   # success
        except OperationError as err:
            if not err.match(space, space.w_SyntaxError):
                raise

        try:
            self.compile(source + "\n", filename, mode, flags)
            return None   # expect more
        except OperationError as err1:
            if not err1.match(space, space.w_SyntaxError):
                raise

        try:
            self.compile(source + "\n\n", filename, mode, flags)
            raise     # uh? no error with \n\n.  re-raise the previous error
        except OperationError as err2:
            if not err2.match(space, space.w_SyntaxError):
                raise

        if space.eq_w(err1.get_w_value(space), err2.get_w_value(space)):
            raise     # twice the same error, re-raise

        return None   # two different errors, expect more


class PyCodeCompiler(AbstractCompiler):
    """Base class for compilers producing PyCode objects."""

    def getcodeflags(self, code):
        """Return the __future__ compiler flags that were used to compile
        the given code object."""
        if isinstance(code, pycode.PyCode):
            return code.co_flags & self.compiler_flags
        else:
            return 0


class PythonAstCompiler(PyCodeCompiler):
    """Uses the stdlib's python implementation of compiler

    XXX: This class should override the baseclass implementation of
         compile_command() in order to optimize it, especially in case
         of incomplete inputs (e.g. we shouldn't re-compile from scratch
         the whole source after having only added a new '\n')
    """
    def __init__(self, space, override_version=None):
        PyCodeCompiler.__init__(self, space)
        self.future_flags = future.futureFlags_3_5
        self.parser = pyparse.PythonParser(space, self.future_flags)
        self.additional_rules = {}
        self.compiler_flags = self.future_flags.allowed_flags

    def compile_ast(self, node, filename, mode, flags, optimize=-1):
        if mode == 'eval':
            check = isinstance(node, ast.Expression)
        elif mode == 'exec':
            check = isinstance(node, ast.Module)
        elif mode == 'input':
            check = isinstance(node, ast.Interactive)
        else:
            check = True
        if not check:
            raise oefmt(self.space.w_TypeError, "invalid node type")

        fut = misc.parse_future(node, self.future_flags.compiler_features)
        f_flags, f_lineno, f_col = fut
        future_pos = f_lineno, f_col
        flags |= f_flags
        info = pyparse.CompileInfo(filename, mode, flags, future_pos,
                optimize=optimize)
        return self._compile_ast(node, info)

    def _compile_ast(self, node, info):
        space = self.space
        try:
            mod = optimize.optimize_ast(space, node, info)
            code = codegen.compile_ast(space, mod, info)
        except parseerror.SyntaxError as e:
            raise OperationError(space.w_SyntaxError, e.wrap_info(space))
        return code

    def validate_ast(self, node):
        try:
            validate.validate_ast(self.space, node)
        except validate.ValidationError as e:
            raise OperationError(self.space.w_ValueError,
                                 self.space.newtext(e.message))

    def compile_to_ast(self, source, filename, mode, flags):
        info = pyparse.CompileInfo(filename, mode, flags)
        return self._compile_to_ast(source, info)

    def _compile_to_ast(self, source, info):
        space = self.space
        try:
            parse_tree = self.parser.parse_source(source, info)
            mod = astbuilder.ast_from_node(space, parse_tree, info,
                                           recursive_parser=self.parser)
        except parseerror.TabError as e:
            raise OperationError(space.w_TabError,
                                 e.wrap_info(space))
        except parseerror.IndentationError as e:
            raise OperationError(space.w_IndentationError, e.wrap_info(space))
        except parseerror.SyntaxError as e:
            raise OperationError(space.w_SyntaxError, e.wrap_info(space))
        return mod

    def compile(self, source, filename, mode, flags, hidden_applevel=False,
            optimize=-1):
        info = pyparse.CompileInfo(filename, mode, flags,
                hidden_applevel=hidden_applevel, optimize=optimize)
        mod = self._compile_to_ast(source, info)
        return self._compile_ast(mod, info)