Exemple #1
0
    def declare_function(self, function):
        self._error_if_defined(function.name, function)
        if function.name == 'main':
            # TODO: create equivalents of sys.exit() and sys.argv, then
            # recommend them here
            if function.returntype != None:
                raise CompileError("main() must not return anything",
                                   function.location)
            if function.args:
                raise CompileError("main() must not take arguments")

        if function.returntype is None:
            returntype = None
        else:
            returntype = self.evaluate(function.returntype, function)
            if returntype.type is not None:
                raise CompileError(
                    "return types need to be classes, not %s instances" %
                    returntype.type.name, function.returntype.location)

        argnames = [namenode.name for argtype, namenode in function.args]
        for arg in argnames:
            if argnames.count(arg) > 1:
                raise CompileError(
                    "there are %d arguments named '%s'" %
                    (argnames.count(arg), arg), function.location)

        argtypes = [
            self.evaluate(argtype, None) for argtype, name in function.args
        ]
        functype = FunctionType(function.name, argtypes, returntype)
        self._variables[function.name] = Variable(Instance(functype),
                                                  function.location,
                                                  initialized=True)
Exemple #2
0
    def _parse_comma_list(self, start='(', stop=')', parsemethod=None):
        # ( )
        # ( element )
        # ( element , )
        # ( element , element )
        # ( element , element , )
        # ...
        if parsemethod is None:
            parsemethod = self.parse_expression

        start_token = self.tokens.check_and_pop('OP', start)
        if self.tokens.coming_up().startswith(['OP', stop]):
            # empty list
            return ([], self.tokens.pop())

        elements = []
        while True:
            if self.tokens.coming_up().startswith(['OP', ',']):
                raise CompileError("don't put a ',' here",
                                   self.tokens.coming_up().location)
            elements.append(parsemethod())

            if self.tokens.coming_up().startswith(['OP', stop]):
                return (elements, self.tokens.pop())

            comma = self.tokens.check_and_pop('OP', ',')
            if self.tokens.coming_up().startswith(['OP', ',']):
                raise CompileError(
                    "two ',' characters",
                    Location.between(comma, self.tokens.coming_up()))

            if self.tokens.coming_up().startswith(['OP', stop]):
                return (elements, self.tokens.pop())
Exemple #3
0
    def check_and_pop(self, kind, value=None):
        if value is not None and self.coming_up().value != value:
            raise CompileError("this should be '%s'" % value,
                               self.coming_up().location)

        if self.coming_up().kind != kind:
            raise CompileError(
                "this should be %s" % utils.add_article(kind.lower()),
                self.coming_up().location)

        return self.pop()
Exemple #4
0
    def parse_expression(self):
        coming_up = self.tokens.coming_up()
        if coming_up.kind == 'NAME':
            # hello
            result = self.parse_name()
        elif coming_up.kind == 'STRING':
            # "hello"
            result = self.parse_string()
        elif coming_up.kind == 'INTEGER':
            # 123
            result = self.parse_integer()
        elif coming_up.startswith(['OP', '(']):
            result = self.parse_parentheses()
        else:
            raise CompileError(
                "this should be variable name, string, integer or '('",
                coming_up.location)

        # check for function calls, this is a while loop to allow
        # function calls like thing()()()
        while self.tokens.coming_up().startswith(['OP', '(']):
            args, stop_token = self._parse_comma_list('(', ')')
            result = FunctionCall(Location.between(result, stop_token), result,
                                  args)

        return result
Exemple #5
0
 def parse_name(self, check_for_keywords=True):
     # thing
     token = self.tokens.check_and_pop('NAME')
     if check_for_keywords and token.value in _KEYWORDS:
         raise CompileError(
             "%s is not a valid variable name because it has a "
             "special meaning" % token.value, token.location)
     return Name(token.location, token.value)
Exemple #6
0
    def evaluate(self, expression, source_statement, *, allow_no_value=False):
        """Pseudo-run an expression.

        The source_statement should be the statement node that the
        expression node comes from. It will be added to a variable's
        used_by list if a variable needs to be evaluated. Set it to None
        if you don't want to add the statement to used_by lists.
        """
        if isinstance(expression, ast.Name):
            var = self._get_var(expression.name, expression.location)
            if source_statement is not None:
                var.used_by.append(source_statement)
            return var.value

        if isinstance(expression, ast.Integer):
            return Instance(INT_TYPE)

        if isinstance(expression, ast.String):
            return Instance(STRING_TYPE)

        if isinstance(expression, ast.FunctionCall):
            func = self.evaluate(expression.function, source_statement)
            if not isinstance(func.type, FunctionType):
                raise CompileError("this is not a function",
                                   expression.function.location)

            args = [
                self.evaluate(arg, source_statement) for arg in expression.args
            ]
            if [arg.type for arg in args] != func.type.argtypes:
                good = ', '.join(type_.name for type_ in func.type.argtypes)
                bad = ', '.join(arg.type.name for arg in args)
                raise CompileError(
                    "should be {name}({}), not {name}({})".format(
                        good, bad, name=func.type.name), expression.location)

            if func.type.returntype is None:
                if not allow_no_value:
                    raise CompileError("this returns nothing",
                                       expression.location)
                return None
            else:
                return Instance(func.type.returntype)

        raise NotImplementedError(expression)  # pragma: no cover
Exemple #7
0
    def _get_var(self, name, location, *, require_initialized=True):
        """Get the value of a variable.

        If mark_used is true, the variable won't look like it's
        undefined. An error is raised if require_initialized is true and
        the value doesn't necessarily have a value yet.
        """
        try:
            var = self._variables[name]
        except KeyError:
            raise CompileError("no variable named '%s'" % name, location)

        if require_initialized and not var.initialized:
            # TODO: better error message
            raise CompileError(
                "variable '%s' might not have a value yet" % name, location)

        return var
Exemple #8
0
def check(tokens):
    brace_stack = []
    for token in tokens:
        if token.kind != 'OP':
            continue

        if token.value in _opening2closing:
            brace_stack.append(token)
        elif token.value in _closing2opening:
            opening = _closing2opening[token.value]
            if not brace_stack:
                raise CompileError("missing '%s'" % opening, token.location)

            open_token = brace_stack.pop()
            if open_token.value != opening:
                raise CompileError(
                    "should be '%s'" % _opening2closing[open_token.value],
                    token.location)

    if brace_stack:
        # i'm not sure if complaining about the outermost brace is the
        # right thing to do, but pypy does it...
        #
        # $ cat > test.py
        # (        # one
        #     (    # two
        #   )      # three
        # ^D
        # $ bin/pypy3 test.py
        # File "test.py", line 1
        #     (        # one
        #     ^
        # SyntaxError: parenthesis is never closed
        outermost = brace_stack[0]
        raise CompileError(
            "missing '%s'" % _opening2closing[brace_stack[0].value],
            brace_stack[0].location)
Exemple #9
0
    def _error_if_defined(self, name, node):
        """Raise CompileError if a variable exists already.

        The node's start and end will be used in the error message if
        the var exists.
        """
        # TODO: include information about where the variable was defined
        if name not in self._variables:
            return

        variable = self._variables[name]
        what = ('function' if isinstance(variable.value.type, FunctionType)
                else 'variable')
        raise CompileError("there's already a %s named '%s'" % (what, name),
                           node.location)
Exemple #10
0
def check(ast_nodes, warn_callback):
    # must not be an iterator because this loops over it several times
    assert ast_nodes is not iter(ast_nodes)

    for node in ast_nodes:
        if not isinstance(node, ast.FunctionDef):
            # TODO: allow global vars and get rid of main functions
            raise CompileError("only function definitions can be here",
                               node.location)
    if 'main' not in (func.name for func in ast_nodes):
        raise CompileError("there's no main() function", None)

    global_scope = Scope(_BUILTIN_SCOPE, None, warn_callback=warn_callback)

    # all functions need to be declared before using them, so we'll just
    # forward-declare everything
    for func in ast_nodes:
        global_scope.declare_function(func)
    for func in ast_nodes:
        global_scope.execute_function_def(func)

    # ast nodes are mutated too, so i think it makes sense to mutate
    # everything instead of making new objects
    ast_nodes[:] = global_scope.output
Exemple #11
0
    def parse_file(self):
        while True:
            try:
                self.tokens.coming_up(1)
            except EOFError:
                break

            try:
                yield from self.parse_statement()
            except EOFError:
                # underline 3 blanks after last token
                last_location = self.tokens.last_popped.location
                mark_here = Location(last_location.end, last_location.end + 3,
                                     last_location.lineno)

                # python abbreviates this as EOF and beginners don't
                # understand it, but i guess this one is good enough
                raise CompileError("unexpected end of file", mark_here)
Exemple #12
0
 def warn(self, *args, **kwargs):
     self._warn_callback(CompileError(*args, **kwargs))
Exemple #13
0
    def execute(self, statement):
        """Pseudo-run a statement."""
        if isinstance(statement, ast.Declaration):
            self._error_if_defined(statement.name, statement)
            assert self.kind == 'inner', "global vars aren't supported yet"

            vartype = self.evaluate(statement.type, statement)
            assert vartype is not None
            if vartype.type is not None:
                raise CompileError(
                    "variable types need to be classes, not %s instances" %
                    vartype.type.name, statement.type.location)

            var = Variable(Instance(vartype),
                           statement.location,
                           used_by=[statement])
            self._variables[statement.name] = var

        elif isinstance(statement, ast.Assignment):
            assert isinstance(statement.target, ast.Name)  # TODO

            try:
                variable = self._get_var(statement.target.name,
                                         statement.target.location,
                                         require_initialized=False)
            except CompileError:
                value = self.evaluate(statement.value, statement)
                raise CompileError(
                    "you need to declare '{varname}' first, "
                    "e.g. '{typename} {varname}'".format(
                        typename=value.type.name,
                        varname=statement.target.name), statement.location)

            variable.used_by.append(statement)

            if self._find_scope(statement.target.name).kind != 'inner':
                assert isinstance(variable.value.type, FunctionType)
                raise CompileError("functions can't be changed like this",
                                   statement.target.location)

            new_value = self.evaluate(statement.value, statement)
            if new_value.type != variable.value.type:
                correct_typename = utils.add_article(
                    "function" if isinstance(variable.value.type, FunctionType
                                             ) else variable.value.type.name)
                wrong_typename = utils.add_article("function" if isinstance(
                    new_value.type, FunctionType) else new_value.type.name)

                # FIXME: it's possible to end up with something like
                # "myvar needs to be a function, not a function"
                raise CompileError(
                    "'%s' needs to be %s, not %s" %
                    (statement.target.name, correct_typename, wrong_typename),
                    statement.location)

            self._variables[statement.target.name].initialized = True

        elif isinstance(statement, ast.If):
            subscope = Scope(self, self.returntype)
            for substatement in statement.body:
                substatement.execute(statement)
            subscope.check_unused_vars()
            statement.body = subscope.output

        elif isinstance(statement, ast.FunctionDef):
            assert self.kind != 'builtin'
            raise CompileError("cannot define a function inside a function",
                               statement.location)

        elif isinstance(statement, ast.Return):
            value = self.evaluate(statement.value, statement)
            if value.type != self.returntype:
                raise CompileError(
                    "this function should return %s, not %s" %
                    (utils.add_article(self.returntype.name),
                     utils.add_article(value.type.name)), statement.location)

        elif isinstance(statement, ast.FunctionCall):
            self.evaluate(statement, statement, allow_no_value=True)

        else:
            assert isinstance(statement, (ast.Name, ast.Integer, ast.String))
            self.warn("this does nothing", statement.location)
            self.evaluate(statement, None)  # raises errors if needed
            return  # don't append it to self.output

        self.output.append(statement)