Skip to content
Snippets Groups Projects
Environment.py 5.59 KiB
import string

class Env:
    nodeToNamespace = dict({
        'FieldNode': 'fieldDcl',
        'InterNode': 'type',
        'ClassNode': 'type',
        'MethodNode': 'method',
        'VarDclNode': 'expr'

    })

    def __init__(self, parentEnv):
        self.parentEnv = parentEnv
        self.map = {}
                #  entries in the map:
                # type: (typeName, 'type') -> node
                # method: (methodName. 'method') -> {paramTypes -> node} for method overloding
                # expr: (name, 'expr') -> node

    def addtoEnv(self, node):
        # use node.__class__.__name__ to get namespace
        nodeType = node.__class__.__name__
        if nodeType not in self.nodeToNamespace:
            namespace = 'expr'
        else:
            namespace = self.nodeToNamespace.get(nodeType)

        # Add to Env
        key = (node.name, namespace)
        # add entry in nested map for method overloding
        if namespace == 'method':
            if key not in self.map:
                self.map[key] = {}
            self.map[key][node.paramTypes] = node
        else:
            if key in self.map:
                raise Exception('ERROR: Declaration of {} is already in current Environment'.format(node.name))
            if namespace == 'expr':
                self.map[key] = (False, node)
            else:
                self.map[key] = node


    def getNode(self, name, namespace):
        key = (name, namespace)

        if key in self.map:
            return self.map.get(key)
        elif self.parentEnv:
            return self.parentEnv.getNode(name, namespace)
        raise Exception("ERROR: Can't find definition of {} in the Environment".format(name))

    # A wrapper around getNode to find if node exists in environment already 
    def findNode(self, name, namespace):
        try: 
            self.getNode(name, namespace) 
        except:
            return False # node is not found in environment 
        
        return True 


###################################

class GlobalEnv(Env):
    def __init__(self):
        super().__init__(None)
        #self.map = {} # {Canonical name -> node}
        self.packageMap = {} # {packageName -> {(simpleName, namespace) -> node}}
                            #   A map of maps
                            #   contains duplicate info as self.map for easy searching

    # input: Compiliation Unit Node (CompNode)
    def addtoEnv(self, node):
        pName = node.packageName
        typeDcl = node.typeDcl
        typeDclType = typeDcl.__class__.__name__
        # at global level there are only class and interface
        namespace = self.nodeToNamespace.get(typeDclType)

        # Add to map
        mapKey = pName + '.'  + typeDcl.name
        if mapKey in self.map:
            raise Exception('ERROR: Declaration of {} is already in current Environment'.format(pName + '.'  + typeDcl.name))
        self.map[mapKey] = typeDcl

        # Add to packageMap
        pMapKey = (typeDcl.name, namespace)
        if not pName in self.packageMap:
            self.packageMap[pName] = {}

        if pMapKey in self.packageMap[pName]:
            raise Exception('ERROR: Declaration of {} is already in current Environment'.format(pName + '.'  + typeDcl.name))
        self.packageMap[pName][pMapKey] = typeDcl

    # Use getNode() from base class to get node using Canonical Name (full name)
    def getNode(self, key, imported, packageName):
        # TODO: error if ambiguous
        # TODO: not sure whether name is canonical or not????
        #  i.e. there are more than one of this type. Currently it only gets the first occurence
        name = key[0]
        
        # 1. enclosing class/interface
        #    - already did
        # 2. single-type import
        if name in imported and name in self.map:
            return self.map.get(name)
        # 3. type in the current package
        full = packageName + '.' + name
        # print(full)
        if full in self.map:
            return self.map.get(full)
        # 4. import-on-demand
        for i in imported:
            if '*' in i:
                full = i.replace("*", name)
                if full in self.map:
                    return self.map.get(full)
        # imported implicitly: java.io.*, java.lang.*, java.util.*
        implicitly = ['java.lang.', 'java.io.', 'java.util.']
        for i in implicitly:
            if i in name and name in self.map:
                return self.map.get(name)
            elif (i+name) in self.map:
                return self.map.get(i+name)

        raise Exception("ERROR: Can't find definition of {} in the Environment".format(key))

    # method for getting all the nodes under a package (import All)
    # returns a dict of types under that package name
    def getNodesByPackage(self, pName):
        if not pName in self.packageMap:
            raise Exception("ERROR: Can't find definition of package {} in the Environment".format(pName))
        return self.packageMap.get(pName)

###################################

class CompEnv(Env):
    def __init__(self, parentEnv, imported, packageName):
        super().__init__(parentEnv) # globalEnv is parentEnv
        self.imported = imported # list of strings that have been imported in this comp
        self.packageName = packageName # string of the current file's package name

    def getNode(self, name, namespace):
        key = (name, namespace)

        if key in self.map:
            return self.map.get(key)
        elif self.parentEnv: # globalEnv
            return self.parentEnv.getNode(key, self.imported, self.packageName)
        raise Exception("ERROR: Can't find definition of {} in the Environment".format(name))