git @ Cat's Eye Technologies Dieter / master src / dieter / context.py
master

Tree @master (Download .tar.gz)

context.py @masterraw · history · blame

# -*- coding: utf-8 -*-

"""
context.py -- typing contexts for the Dieter programming language.
$Id: context.py 492 2010-04-26 21:16:03Z cpressey $
"""

from dieter.type import Type, TypeProc
import logging

logger = logging.getLogger("context")


class TypingError(Exception):
    """An exception indicating that a type incompatibility was encountered."""
    pass


class TypingContext(object):
    """Class that associates names with types in a given scope.

    A TypingContext object is passed along the AST during typechecking,
    being modified as a result.  Each TypingContext can have a parent
    TypingContext; lookups for types go to it if not found in the current one.
    
    It's actually more like a symbol table, because it associates
    every symbol with some information about it, even if it's not
    a type...

    """

    def __init__(self, parent):
        self.map = {}
        self.parent = parent
        self.module = None
        self.TypingError = TypingError

    def associate(self, name, type):
        """Associate the given name with the given type in this context.

        The name must be a name not already present in this context.

        """
        if name in self.map:
            raise TypingError("name " + name + " already bound to " + str(type))
        assert isinstance(type, Type)
        logger.info("associating " + name + " with " + str(type))
        self.map[name] = type

    def associate_qualifier(self, name):
        logger.info("registering " + name + " as a type qualifier")
        self.map[name] = "qualifier"

    def get_type(self, name):
        if name in self.map:
            logger.info("got type for " +name+ ": " + str(self.map[name]))
            return self.map[name]
        elif self.parent != None:
            logger.info("name " + name + " not found, trying parent context")
            return self.parent.get_type(name)
        else:
            raise TypingError("name " + name + " totally not found")

    def dump(self):
        for k, v in self.map.iteritems():
            if isinstance(v, Type):
                print(k + " : " + str(v))  # return string instead?
            else:
                print(k + " : " + v)

    def assert_equiv(self, inwhat, s, t):
        if not s.unify(t):
            raise TypingError("in " + inwhat + ": " + str(s) +
                              " not compatible with " + str(t))

    def check_call(self, proc_name, arg_types):
        """Check that a call to the named procedure or function in
        this context, with the given argument types, is legal.

        We start by obtaining the (fully general) type of the
        procedure being called, and instantiating it so that we
        can bind any type variables in it.
        
        If unification succeeds, we return the (bound) return type
        of the called procedure.

        """
        proc_type = self.get_type(proc_name)
        if not proc_type.is_callable():
            raise TypingError(self.name + ":" + str(proc_type) + " is not a procedure type")
        proc_type = proc_type.clone()
        return_type = proc_type.return_type

        # create a putative type representing the proc we are trying to call
        putative_type = TypeProc(return_type)
        for arg_type in arg_types:
            putative_type.add_arg_type(arg_type)

        logger.info("check_call to '" + proc_name + "' which has type " + str(proc_type))
        logger.info("putative type is " + str(putative_type))

        # try to unify
        if proc_type.unify(putative_type):
            return proc_type.return_type
        else:
            raise TypingError(str(proc_type) + " could not unify with " + str(putative_type))

    def new_module(self, module):
        """Create a new subcontext for a module.

        Given a module AST, return a new subcontext to
        contain things that cannot be accessed outside the
        module, such as its module-local variables.  The
        current context is made the parent of the returned
        subcontext, so any searches in the subcontext will
        search the parent if the term is not found.

        """
        subcontext = TypingContext(self)
        subcontext.module = module
        return subcontext

    def get_module(self):
        if self.module != None:
            return self.module
        elif self.parent != None:
            return self.parent.get_module()
        else:
            # XXX this is more of an internal error than a TypingError
            raise TypingError("get_module: no module in context")

    def new_procedure(self, procedure):
        """Create a new subcontext for a module.

        Like new_module, but for procedures.

        """
        subcontext = TypingContext(self)
        subcontext.procedure = procedure
        return subcontext

    def get_procedure(self):
        if self.procedure != None:
            return self.procedure
        elif self.parent != None:
            return self.parent.get_procedure()
        else:
            # XXX this is more of an internal error than a TypingError
            raise TypingError("get_procedure: no procedure in context")

    def global_context(self):
        if self.parent != None:
            return self.parent.global_context()
        else:
            return self