# -*- 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