def __init__(self): self.stack = [] # last element in array is always top of stack. self.funcStack = [] self.rootNode = None # also contains additional data self.protObjName = None # textual names of each endpoint self.endpoint1 = None self.endpoint2 = None # the line number that the names appear on self.endpoint1LineNo = None self.endpoint2LineNo = None # the ast nodes associated with each endpoint self.endpoint1Ast = None self.endpoint2Ast = None # handles keeping track of msgSend and msgReceive functions # (both their specification in the trace section as well as # their definitions in endpoint sections. self.traceManager = TraceLineManager(self) # this keeps track of which endpoint body section we're type # checking through. It's None if we are not currently type # checking an endpoint's body section. It's a string # otherwise. Used for type checking with trace lines (in # self.traceManager). self.currentEndpointName = None self.inOnComplete = False self.inSequencesSection = False # array of tuples: first element is string endpoint name, # second element is string function name. self.sequenceSectionNameTuples = [] # used to type check return statements to ensure that a # function actually returns the type that it says it will. # msg_send_seq_func, msg_recv_seq_func, public_func, # on_create, private_func self.currentFunctionNode = None # allowed to have placeholder extAssigns and extCopies in lhs # of assignment statement to accommodate tuples. Ie, you're # allowed to do extAssign _ to some_ext = some_func(); In the # rest of the code, you're not allowed to use and lhs_assign # statement. self.in_lhs_assign = False # indices are the struct names. values are the types of the # struct with that name self.struct_type_dict = {} # initialize endpoint builtin methods self.initBuiltinMethods()
class TypeCheckContextStack(object): def __init__ (self): self.stack = []; #last element in array is always top of stack. self.funcStack = []; self.rootNode = None; #also contains additional data self.protObjName = None; # textual names of each endpoint self.endpoint1 = None; self.endpoint2 = None; # the line number that the names appear on self.endpoint1LineNo = None; self.endpoint2LineNo = None; # the ast nodes associated with each endpoint self.endpoint1Ast = None; self.endpoint2Ast = None; #handles keeping track of msgSend and msgReceive functions #(both their specification in the trace section as well as #their definitions in endpoint sections. self.traceManager = TraceLineManager(self); #this keeps track of which endpoint body section we're type #checking through. It's None if we are not currently type #checking an endpoint's body section. It's a string #otherwise. Used for type checking with trace lines (in #self.traceManager). self.currentEndpointName = None; self.inOnComplete = False; self.inSequencesSection = False; # array of tuples: first element is string endpoint name, # second element is string function name. self.sequenceSectionNameTuples = []; # used to type check return statements to ensure that a # function actually returns the type that it says it will. # msg_send_seq_func, msg_recv_seq_func, public_func, # on_create, private_func self.currentFunctionNode = None; # allowed to have placeholder extAssigns and extCopies in lhs # of assignment statement to accommodate tuples. Ie, you're # allowed to do extAssign _ to some_ext = some_func(); In the # rest of the code, you're not allowed to use and lhs_assign # statement. self.in_lhs_assign = False # indices are the struct names. values are the types of the # struct with that name self.struct_type_dict = {} def setRootNode(self,root): if self.rootNode != None: errMsg = '\nBehram error: should not set root node after it has '; errMsg += 'already been set.\n'; print(errMsg); assert(False); self.rootNode = root; def checkRepeatedSequenceLine(self): ''' @return{None or TypeCheckError} -- None if no error, TypeCheckError if more than one trace line have the same name. ''' return self.traceManager.checkRepeatedSequenceLine(); def getCurrentEndpoint(self): if self.currentEndpointName == None: errMsg = '\nBehram error: should be type checking an endpoint '; errMsg += 'to call getCurrentEndpoint in typestack.\n'; print(errMsg); assert(False); if self.currentEndpointName == self.endpoint1: return self.endpoint1Ast; elif self.currentEndpointName == self.endpoint2: return self.endpoint2Ast; errMsg = '\nBehram error: no matching endpoint for currentEndpointName. '; errMsg += 'Cannot return current endpoint.\n'; print(errMsg); assert(False); def getOtherEndpointName(self): if (self.currentEndpointName == None) or (self.endpoint1 == None) or (self.endpoint2 == None): errMsg = '\nBehram error: should not call getOtherEndpointName in '; errMsg += 'astTypeCheckStack.py unless we are currently in an endpoint '; errMsg += 'and all endpoints are defined.\n'; errPrint(errMsg); assert(False); if (self.currentEndpointName != self.endpoint1): return self.endpoint1; return self.endpoint2; def add_struct_type(self,struct_name,struct_type): ''' @returns {None or String} --- String if there's an error (the string is the error message). None if there's no error. ''' err_msg = None if struct_name in self.struct_type_dict: err_msg = 'Error. Already have a struct named ' err_msg += struct_name + '.' self.struct_type_dict[struct_name] = struct_type return err_msg def get_struct_type(self,struct_name,external): ''' @param {bool} external @returns{type dict or None} --- None if struct_name has not been declared by user. type dict if it has (where type dict is the declared type of that node). ''' s_type = self.struct_type_dict.get(struct_name,None) if s_type == None: return s_type # deep copy type so that marking as external or not will not # affect any other type. s_type = pickle.loads(pickle.dumps(s_type)) set_external(s_type,external) return s_type def addCurrentFunctionNode(self,node): ''' Sets the current function we're in so that can check return types when we get to them. ''' if ((node.label != AST_PUBLIC_FUNCTION) and (node.label != AST_PRIVATE_FUNCTION) and (node.label != AST_ONCREATE_FUNCTION) and (node.label != AST_MESSAGE_SEND_SEQUENCE_FUNCTION ) and (node.label != AST_MESSAGE_RECEIVE_SEQUENCE_FUNCTION) and (node.label != AST_ONCOMPLETE_FUNCTION)): errMsg = '\nBehram error: adding internal or public node with incorrect '; errMsg += 'type.\n'; errPrint(errMsg); assert(False); self.currentFunctionNode = node; def checkReturnStatement(self,returnNode,check_type_mismatch_func): ''' @param {function} check_type_mismatch_func ... should just be typeCheck.checkTypeMismatch. ''' if (returnNode.label != AST_RETURN_STATEMENT): errMsg = '\nBehram error: trying to check a '; errMsg += 'return statement without a return statement node.\n'; errPrint(errMsg); assert(False); if self.currentFunctionNode == None: errMsg = '\nBehram error: any return statement should have a '; errMsg += 'currentFunctionNode to compare to.\n'; print(errMsg); assert(False); return_tuple_node = returnNode.children[0] # list of the types that we're actually returning return_type_list = [] for single_node in return_tuple_node.children: # takes care of case where we are returning a function # call if ((single_node.label == AST_FUNCTION_CALL) and (not is_wildcard_type(single_node.type))): func_returned_type_array = get_type_array_from_func_call_returned_tuple_type( single_node.type) for ind_tuple_return_type in func_returned_type_array: return_type_list.append(ind_tuple_return_type) else: return_type_list.append(single_node.type) returnStatementType = returnNode.children[0].type; if ((self.currentFunctionNode.label == AST_MESSAGE_SEND_SEQUENCE_FUNCTION) or (self.currentFunctionNode.label == AST_MESSAGE_RECEIVE_SEQUENCE_FUNCTION)): if len(return_type) > 0: err_msg = 'Error. Cannot return a value from a sequence function.' nodes= [returnNode]; return TypeCheckError(nodes,err_msg); # no error because message sequences are not declared to # return anything and this return statement does not. return None # check public and privates for return statement... function_returns_type_node = self.currentFunctionNode.children[1]; # the declared return type of this function function_returns_type_list = [] for single_type in function_returns_type_node.children: function_returns_type_list.append(single_type.type) funcName = self.currentFunctionNode.children[0].value; if len(function_returns_type_list) != len(return_type_list): err_msg = 'Error. ' + funcName + ' expects to return ' err_msg += str(len(function_returns_type_list)) + ' arguments. ' err_msg += 'Instead, you returned ' + str(len(return_type_list)) err_msg += ' arguments.' err_nodes = [returnNode, function_returns_type_node] return TypeCheckError(err_nodes,err_msg) for return_index in range(0,len(return_type_list)): declared_return_type = function_returns_type_list[return_index] actual_return_type = return_type_list[return_index] if check_type_mismatch_func( returnNode,declared_return_type,actual_return_type,self,''): err_msg = 'Incorrect return type in ' + funcName + '. ' if len(return_type_list) == 1: err_msg += 'Expected type ' err_msg += dict_type_to_str(declared_return_type) err_msg += ', but actually returned type ' err_msg += dict_type_to_str(actual_return_type) err_msg += '.' else: err_msg += 'The ' + str(return_index + 1) + ' tuple return ' err_msg += 'element expected a type of ' err_msg += dict_type_to_str(declared_return_type) err_msg += ', but actually returned type of ' err_msg += dict_type_to_str(actual_return_type) err_msg += '.' err_nodes = [returnNode, function_returns_type_node] return TypeCheckError(err_nodes, err_msg) return None; def checkTraceItemInputOutput(self): ''' run through all trace lines to see if the message outputs match the message inputs. @return {None or TypeCheckError} -- None if inputs and outputs agree. TypeCheckError if they do not. ''' return self.traceManager.checkTraceItemInputOutput(); def pushContext(self): self.stack.append(Context()); self.funcStack.append(FuncContext()); def popContext(self): if (len(self.stack) <= 0): errPrint('\nBehram Error. Empty type context stack. Cannot pop\n'); assert(False); self.stack.pop(); self.funcStack.pop(); self.currentFunctionNode = None; def getIdentifierType(self,identifierName): ''' @param {String} identifierName The name of the identifier that we're looking up in the memory store. @returns {tuple} (None,None) if have no type information for that identifier. Otherwise, ( a, b). a: String with type name. b: String name of the endpoint that controls the shared variable (or None, if no one controls variable. ''' for s in reversed(range(0,len(self.stack))): lookupType, controlledBy= self.stack[s].getIdentifierType(identifierName); if (lookupType != None): return lookupType,controlledBy; return None,None; def checkUndefinedTraceItems(self): ''' Runs through all elements in trace line to ensure that if a msgSend or msgReceive is declared in the trace section, it was actually defined in an endpoint section. @return{None or TypeCheckError} -- None if no error, TypeCheckError otherwise. ''' return self.traceManager.checkUndefinedMsgSendOrReceive(); def checkCollision(self,identifierName,astNode): ''' @param {string} identifierName -- The name of the variable/function we want to determine if there is a collision for. @param {AstNode} astNode -- Needed to pass into constructor of CollisionObject (returned if there is a collision; see below). @return none if no variable or function in the current or parent contexts has the same name as identifierName. Otherwise, returns a CollisionObject, from which can get error message information. ''' if (identifierName == 'Print'): return CollisionObject(astNode,None,None); idElement = self.getIdentifierElement(identifierName); funcMatchObj = self.getFuncIdentifierType(identifierName); if (idElement == None) and (funcMatchObj == None): #no collision return None; funcElement = None; if (funcMatchObj != None): funcElement = funcMatchObj.element; #there was a collision: generate CollisionObject and return. return CollisionObject(astNode,idElement,funcElement); def getIdentifierElement(self,identifierName): ''' Usage: should only be called internal to this class. @param {String} identifierName The name of the identifier that we're looking up in the memory store. @returns None if have no type information for that identifier. Otherwise, returns ContextElement ''' for s in reversed(range(0,len(self.stack))): lookupType= self.stack[s].getIdentifierElement(identifierName); if (lookupType != None): return lookupType; return None; def getFuncIdentifierType(self,identifierName): ''' @returns {FuncMatch object or None} -None if there is no declared function with same name -FuncMatch object if there is a function with the same name. ''' for s in reversed(range(0,len(self.funcStack))): lookupType = self.funcStack[s].getFuncIdentifierType(identifierName); if (lookupType != None): return lookupType; return None; def addIdentifier(self,identifierName,identifierType,controlledBy,astNode,lineNum = None): ''' @param {String} controlledBy --- name of the endpoint that is authoritative for this variable if it's a shared variable. None if it's not a shared variable or if no one is authoritative for it. ''' if(len(self.stack) <= 1): errPrint('\nBehram Error. Cannot insert into type check stack because stack is empty.\n'); assert(False); self.stack[-1].addIdentifier(identifierName,identifierType,controlledBy,astNode,lineNum); def isEndpoint(self,endpointName): return ((endpointName == self.endpoint1) or (endpointName == self.endpoint2)); def addFuncIdentifier(self,functionName,functionType,functionArgTypes,astNode,lineNum=None): ''' @param {string} functionName: name of function @param{list of type dicts} functionType --- The return type of this function. We're using a list to support returning tuples. Each element of the list is the return type of that element of the tuple. @param {list} functionArgTypes: ordered (from left to right) list of types for function arguments. @param {int} lineNum: line number that function was declared on. @returns {None or TyepCheckError} -- None if nothing is wrong with trace, TypeCheckError otherwise. ''' if len(self.funcStack) <= 1: errMsg = '\nBehram Error. Cannot insert into type '; errMsg += 'check stack because stack is empty.\n'; errPrint(errMsg); assert(False); #if it's a msgSend function or msgReceive function, then #notify the traceLineManager that a msgSend or msgreceive #function has been defined; currentEndpointName = self.currentEndpointName; if (currentEndpointName == None): errMsg = '\nBehram error: should ony be adding a '; errMsg += 'func identifier in the body of an endpoint section.\n'; errPrint(errMsg); assert(False); traceError = None; if astNode.label == AST_MESSAGE_SEND_SEQUENCE_FUNCTION: traceError = self.traceManager.addMsgSendFunction(astNode,currentEndpointName); elif astNode.label == AST_MESSAGE_RECEIVE_SEQUENCE_FUNCTION: traceError = self.traceManager.addMsgRecvFunction(astNode, currentEndpointName); if (traceError != None): return traceError; #add the function identifier itself to function context. traceError = self.funcStack[-1].addFuncIdentifier( functionName,functionType,functionArgTypes,astNode,lineNum); return traceError; def setCurrentEndpointName(self,endpointName): ''' When begin type checking the body of an endpoint function, call this with the endpoint's name. ''' self.currentEndpointName = endpointName; def unsetCurrentEndpointName(self): ''' After finished type checking the body of an endpoint function, call this function to unset currentEndpointname. ''' self.currentEndpointName = None; def setAstTraceSectionNode(self,traceSectionAstNode): ''' @param {AstNode} traceSectionAstNode ''' self.traceManager.setAstTraceSectionNode(traceSectionAstNode); def addTraceLine(self,traceLineAstNode): ''' @param{AstNode} traceLineAst ''' return self.traceManager.addTraceLine(traceLineAstNode);