def visitMath(self, ctx: BSParser.MathContext): deff = self.visitVariableDefinition(ctx.variableDefinition()) symbol = Symbol(deff['name'], self.scope_stack[-1], ChemTypeResolver.numbers()) for use in ctx.primary(): var = self.visitPrimary(use) # This places any constants into the global symbol table. # By doing this, it makes it significantly easier to handle # arithmetic later in the compilation process. if 'value' in var.keys() and not self.symbol_table.get_global( var['name']): globalz = Symbol(var['name'], 'global', ChemTypeResolver.numbers()) globalz.value = Number(var['name'], 1, var['value']) self.symbol_table.add_global(globalz) if not ChemTypeResolver.is_number_in_set(var['types']): local = self.symbol_table.get_local(var['name']) if not local: raise UndefinedVariable("{} is not defined.".format( var['name'])) local.types.update(ChemTypeResolver.numbers()) if ChemTypes.UNKNOWN in local.types: local.types.remove(ChemTypes.UNKNOWN) self.symbol_table.update_symbol(local) self.symbol_table.add_local(symbol) return None
def visitNumberAssignment(self, ctx: BSParser.NumberAssignmentContext): deff = self.visitVariableDefinition(ctx.variableDefinition()) symbol = Symbol(deff['name'], self.scope_stack[-1], ChemTypeResolver.numbers()) self.symbol_table.add_local(symbol) # Yes, this is assigning value to something, # But this is a constant. So we know all # of the values up front. This also makes # the IRVisitor easier to work with. value = self.visitLiteral(ctx.literal()) const = Symbol('CONST_{}'.format(value), 'global', ChemTypeResolver.numbers()) const.value = Number('CONST_{}'.format(value), 1, value) self.symbol_table.add_global(const)
def visitRepeat(self, ctx: BSParser.RepeatContext): # 'repeat value times' is translated in the IR as while (value > 0), with a decrement # appended to the end of the expression block; hence, # to ease translation later, we add a global for const(0) and const(1) if not self.symbol_table.get_global('CONST_0'): globalz = Symbol('CONST_0', 'global', ChemTypeResolver.numbers()) globalz.value = Number('CONST_0', 1, 0) self.symbol_table.add_global(globalz) if not self.symbol_table.get_global('CONST_1'): globalz = Symbol('CONST_1', 'global', ChemTypeResolver.numbers()) globalz.value = Number('CONST_1', 1, 1) self.symbol_table.add_global(globalz) self.visitChildren(ctx)
def visitBinops(self, ctx: BSParser.BinopsContext): op1 = self.visitPrimary(ctx.primary(0)) op2 = self.visitPrimary(ctx.primary(1)) # This places any constants into the global symbol table. # By doing this, it makes it significantly easier to handle # arithmetic later in the compilation process. if 'value' in op1.keys() and not self.symbol_table.get_global( op1['name']): globalz = Symbol(op1['name'], 'global', ChemTypeResolver.numbers()) globalz.value = Number(op1['name'], 1, op1['value']) self.symbol_table.add_global(globalz) if 'value' in op2.keys() and not self.symbol_table.get_global( op2['name']): globalz = Symbol(op2['name'], 'global', ChemTypeResolver.numbers()) globalz.value = Number(op2['name'], 1, op2['value']) self.symbol_table.add_global(globalz)
def visitPrimary(self, ctx: BSParser.PrimaryContext): if ctx.variable(): primary = self.visitVariable(ctx.variable()) else: value = self.visitLiteral(ctx.literal()) # It's a constant, thus, the size must be 1 and index 0. primary = { 'name': "{}{}".format(self.const, value), "index": 0, 'value': value, 'types': ChemTypeResolver.numbers() } return primary
def visitReturnStatement(self, ctx: BSParser.ReturnStatementContext): # It's either a primary or a method call types = set() if ctx.primary(): var = self.visitPrimary(ctx.primary()) local = self.symbol_table.get_local(var['name'], self.scope_stack[-1]) # If we don't have a local, this is a constant. if local: types = self.symbol_table.get_local(var['name']).types else: types = ChemTypeResolver.numbers() elif ctx.methodCall(): method_name, args = self.visitMethodCall(ctx.methodCall()) types = self.symbol_table.functions[method_name].types else: raise UnsupportedOperation( "Only method calls or values are returnable.") return types
def visitDetect(self, ctx: BSParser.DetectContext): deff = self.visitVariableDefinition(ctx.variableDefinition()) self.symbol_table.add_local( Symbol(deff['name'], self.scope_stack[-1], ChemTypeResolver.numbers())) use = self.visitVariable(ctx.variable()) var = self.symbol_table.get_local(use['name']) if not var: raise UndefinedVariable("{} is not defined.".format(use['name'])) module = self.symbol_table.get_global(ctx.IDENTIFIER().__str__()) if not module: raise UndefinedVariable( "{} isn't declared in the manifest.".format( ctx.IDENTIFIER().__str__())) if ChemTypes.MODULE not in module.types: raise UndefinedVariable( "There is no module named {} declared in the manifest.".format( module.name)) if not ChemTypeResolver.is_mat_in_set(var.types): var.types.add(ChemTypes.MAT) self.symbol_table.update_symbol(var) return None
def visitRepeat(self, ctx: BSParser.RepeatContext): # get the (statically defined!) repeat value and add to local symbol table value = self.visitLiteral(ctx) val = { 'name': "REPEAT_{}".format(value), "index": 0, 'value': value, 'types': ChemTypeResolver.numbers() } if 'value' in val.keys() and not self.symbol_table.get_local( val['name']): localz = Symbol(val['name'], 'global', ChemTypeResolver.numbers()) localz.value = Number(val['name'], 1, val['value']) self.symbol_table.add_local(localz) # finished with this block self.functions[self.scope_stack[-1]]['blocks'][ self.current_block.nid] = self.current_block # insert header block for the conditional header_block = BasicBlock() header_label = Label("bsbbr_{}_h".format(header_block.nid)) self.labels[header_label.name] = header_block.nid header_block.add(header_label) self.graph.add_node(header_block.nid, function=self.scope_stack[-1], label=header_label.label) self.functions[self.scope_stack[-1]]['blocks'][ header_block.nid] = header_block self.graph.add_edge(self.current_block.nid, header_block.nid) zero = self.symbol_table.get_global('CONST_0') op = BinaryOp(left={ 'name': val['name'], 'offset': 0, 'size': 1, 'var': self.symbol_table.get_local(val['name']) }, right={ 'name': zero.name, 'offset': 0, 'size': 1, 'var': zero }, op=RelationalOps.GT) condition = Conditional( RelationalOps.GT, op.left, op.right) # Number('Constant_{}'.format(0), 1, 0)) header_block.add(condition) self.control_stack.append(header_block) # set up the true block true_block = BasicBlock() true_label = Label("bsbbr_{}_t".format(true_block.nid)) self.labels[true_label.name] = true_block.nid true_block.add(true_label) self.graph.add_node(true_block.nid, function=self.scope_stack[-1]) self.functions[self.scope_stack[-1]]['blocks'][ true_block.nid] = true_block condition.true_branch = true_label self.graph.add_edge(header_block.nid, true_block.nid) self.current_block = true_block self.visitBlockStatement(ctx.blockStatement()) # repeat is translated to a while loop as: while (exp > 0); # hence, we update exp by decrementing. one = self.symbol_table.get_global('CONST_1') ir = Math( { 'name': val['name'], 'offset': 0, 'size': 1, 'var': self.symbol_table.get_local(val['name']) }, { 'name': val['name'], 'offset': 0, 'size': 1, 'var': self.symbol_table.get_local(val['name']) }, { 'name': one.name, 'offset': 0, 'size': 1, 'var': one }, BinaryOps.SUBTRACT) self.current_block.add(ir) # the block statement may contain nested loops # If so, the current block is the last false block created for the inner-most loop # otherwise, the current block is the true_block created above # Either way, we can pop the control stack to find where to place the back edge # and immediately make the back edge (from 'current block' to the parent parent_block = self.control_stack.pop() self.graph.add_edge(self.current_block.nid, parent_block.nid) # we now deal with the false branch false_block = BasicBlock() false_label = Label("bsbbr_{}_f".format(false_block.nid)) self.labels[false_label.name] = false_block.nid false_block.add(false_label) condition.false_branch = false_label self.graph.add_edge(header_block.nid, false_block.nid) # We are done, so we need to handle the book keeping for # next basic block generation. self.graph.add_node(false_block.nid, function=self.scope_stack[-1]) self.functions[self.scope_stack[-1]]['blocks'][ false_block.nid] = false_block self.current_block = false_block return NOP()
def build_declares(self): if self.types_used == TypesUsed.COMPLEX: types = ChemTypeResolver._available_types else: types = ChemTypeResolver._naive_types types.add(ChemTypes.UNKNOWN) declares = "" defines = "" asserts = "" for name, var in self.symbol_table.globals.items(): """ Declare the constants for all global variables. """ if ChemTypes.UNKNOWN in var.types: var.types.remove(ChemTypes.UNKNOWN) if ChemTypes.INSUFFICIENT_INFORMATION_FOR_CLASSIFICATION in var.types: var.types.remove( ChemTypes.INSUFFICIENT_INFORMATION_FOR_CLASSIFICATION) declares += f"; Declaring constants for: {self.get_smt_name(var).upper()}{self.nl}" for enum in types: declares += f"(declare-const {self.get_smt_name(var, ChemTypes(enum))} Bool){self.nl}" declares += self.nl defines += f"; Defining the assignment of: {self.get_smt_name(var).upper()}{self.nl}" for t in var.types: """ Now we actually state the typing assignment of each variable. """ defines += "(assert (= {} true)){}".format( self.get_smt_name(var, t), self.nl) if self.types_used == TypesUsed.SIMPLE: """ If it's naive, then make sure that unknown is false. In other words, we must have a nat/real/mat type. """ defines += "; Ensure that {} is a not unknown type{}".format( self.get_smt_name(var).upper(), self.nl) defines += "(assert (= {} false)){}".format( self.get_smt_name(var, ChemTypes.UNKNOWN), self.nl) for name, scope in self.symbol_table.scope_map.items(): """ Declare the constants for all local variables. """ for symbol in scope.locals: var = scope.locals[symbol] if ChemTypes.UNKNOWN in var.types and ChemTypes.INSUFFICIENT_INFORMATION_FOR_CLASSIFICATION in var.types: if len(var.types) > 2: var.types.remove( ChemTypes. INSUFFICIENT_INFORMATION_FOR_CLASSIFICATION) var.types.remove(ChemTypes.UNKNOWN) else: if ChemTypes.UNKNOWN in var.types and len(var.types) > 1: var.types.remove(ChemTypes.UNKNOWN) elif ChemTypes.INSUFFICIENT_INFORMATION_FOR_CLASSIFICATION in var.types and len( var.types) > 1: var.types.remove( ChemTypes. INSUFFICIENT_INFORMATION_FOR_CLASSIFICATION) declares += "; Declaring constants for: {}{}".format( self.get_smt_name(var).upper(), self.nl) for enum in types: """ Declare the constants for all scoped variables. """ declares += "(declare-const {} Bool){}".format( self.get_smt_name(var, ChemTypes(enum)), self.nl) declares += self.nl defines += "; Defining the assignment of: {}{}".format( self.get_smt_name(var).upper(), self.nl) for t in var.types: defines += "(assert (= {} true)){}".format( self.get_smt_name(var, t), self.nl) if self.types_used == TypesUsed.SIMPLE: """ If it's naive, then make sure that unknown is false. In other words, we must have a nat/real/mat type. """ defines += "; Ensure that {} is not unknown type{}".format( self.get_smt_name(var).upper(), self.nl) defines += "(assert (= {} false)){}".format( self.get_smt_name(var, ChemTypes.UNKNOWN), self.nl) if var.types & ChemTypeResolver.numbers(): """ Build the asserts for things that are numbers. We will only check naively: in that if we intersect, And we have something, then we know it's a number. """ asserts += self.assert_material(var, False) if var.types & ChemTypeResolver.materials(): """ Build the asserts for things that are numbers. We will only check naively: in that if we intersect, And we have something, then we know it's a mat. """ asserts += self.assert_material(var) self.add_smt( "; ==============={}; Declaring Constants{}; ==============={}". format(self.nl, self.nl, self.nl)) self.add_smt(declares) # self.add_smt("; ==============={}; Declaring typing{}; ==============={}".format(self.nl, self.nl, self.nl)) # self.add_smt(defines) self.add_smt( "; ==============={}; Declaring Asserts{}; ==============={}". format(self.nl, self.nl, self.nl)) self.add_smt(asserts)