def __init__(self, first, second): CompError.__init__(self, "persist call at %s already defined as %s as %s", second.lhs.location.start, first.lhs, first.lhs.location.start) self.first = first self.second = second
def previsit_expression(self, expr, parent): expr.defined = parent.defined if isinstance(expr, ast.FunctionCall): op = OPTYPE_MAP.get(expr.name) if op is None: raise BadFunctionError(expr) expr.optype = op if expr.name == 'persist': if len(expr.args) != 1 or not isinstance( expr.args[0], ast.ConstExpr): raise CompError("Bad persist call at %s", expr.location.start) if not isinstance(parent, ast.AssignStmt): raise PersistDeclarationError(expr) name = str(expr.args[0]) if name in parent.persist: raise PersistTwiceError(parent.persist[name], parent) # Global persist dictionary is now set for this function parent.persist[name] = parent parent.lhs.persist = name if isinstance(expr, ast.VarExpr): # TODO: "Declaration" doesn't really exist anymore. # Move this checking later to IR stage? So the same variable # can be defined in two branches and allowed there. decl = expr.defined.get(expr.var) if decl is None: raise UseBeforeDeclareError(expr.var, expr.location) expr.decl = decl
def main(): if len(sys.argv) != 2: print('Usage: %s [program]' % sys.argv[0]) sys.exit(1) # Some rereouting I guess? So that 'print' uses stderr sys._stdout = sys.stdout # pylint: disable=protected-access sys.stdout = sys.stderr source_name = sys.argv[1] if source_name == '-' and not sys.stdin.isatty(): source = sys.stdin.read() elif os.path.exists(source_name): with open(source_name, 'r') as source_file: source = source_file.read() else: raise CompError("'%s' does not exist", source_name) bsl_codes = run(source) output = bsl_codes['command'].get_program() # Just always do this for now if source_name == '-' or True: sys._stdout.write(output) # pylint: disable=protected-access else: prefix, _ = os.path.splitext(source_name) output_name = prefix + '.buf' with open(output_name, 'w') as output_file: output_file.write(output)
def extract_string(expr): if not isinstance(expr, ConstExpr): raise CompError('%s', expr) if isinstance(expr.value, str): return expr.value elif isinstance(expr.value, int): return str(expr.value) elif isinstance(expr.value, StringExpr): pieces = [] for piece in expr.value.pieces: if isinstance(piece, str): pieces.append(piece) else: # Do this in typechecking as well probably raise CompError('Expected pure string, got %s', expr) return ''.join(pieces) else: raise CompInternalError(expr)
def postvisit_expression(self, expr, children): if isinstance(expr, ast.VarExpr): # This wll just be int # TODO: Boolean variables expr.typ = expr.decl.typ if isinstance(expr, ast.FlagExpr): # TODO: Should do this in declare? flagtype = FLAG_MAP.get(expr.var) if flagtype is None: # TODO: Location? raise CompError('Bad flag %s', expr) expr.typ = flagtype elif isinstance(expr, ast.ConstExpr): if isinstance(expr.value, bool): expr.typ = BaseType.BOOL elif isinstance(expr.value, int): expr.typ = MetaType.INT_OR_STRING else: expr.typ = BaseType.STRING elif isinstance(expr, ast.StringExpr): # TODO: Make a complex string vs non-complex string type? # Just so complex strings can't be compared with each other expr.typ = BaseType.STRING elif isinstance(expr, ast.UnaryExpr): op = OPTYPE_MAP.get(expr.op) if op is None: raise CompError('Internal error. No op called %s', expr) expr.optype = op expr.typ = self.require_func(expr, [expr.arg]) elif isinstance(expr, ast.BinaryExpr): op = OPTYPE_MAP.get(expr.op) if op is None: raise CompError('Internal error. No op called %s', expr) expr.optype = op expr.typ = self.require_func(expr, [expr.lhs, expr.rhs]) elif isinstance(expr, ast.FunctionCall): # optyp gets assigned in declaration visiting expr.typ = self.require_func(expr, expr.args)
def require(self, expr, typ): # INT_OR_STRING should get collapsed into INT or STRING (preferring the former) # I believe this always happen at the usage site, so we don't need weird # constraint-solving complexity. if expr.typ == MetaType.INT_OR_STRING or typ == MetaType.INT_OR_STRING: resolved = self.resolve_intstring(expr.typ, typ) if not resolved: raise TypeMismatchError(expr, typ) return resolved if expr.typ == MetaType.SAME_TYPE or typ == MetaType.SAME_TYPE: raise CompError('Internal error: %s, %s', expr, typ) if expr.typ != typ: raise TypeMismatchError(expr, typ) return expr.typ
def run(source): parser.yacc.has_error = False ast_p = parser.yacc.parse(source, tracking=True) if parser.yacc.has_error: raise CompError("Invalid program") #print repr(ast_p) ast.visit_ast(declare.DeclarationVisitor(), ast_p) ast.visit_ast(typecheck.TypeVisitor(), ast_p) conv = ast_codegen.AstCodegen() ir_program = conv.generate(ast_p) ir_program.merge_empty_blocks() ir_program.remove_dead_blocks() # TODO: This. elems = [] # dataflow.get_undefined(ir_program) if len(elems): elem = elems[0] raise CompError( 'Variable "%s" is used at %s without being defined first', elem, elem.location.start) reg_program = registers.allocate_registers(ir_program) return bsl_codegen.output_bsl_codes(reg_program)
def resolve_intstring(typ1, typ2): if typ1 == MetaType.INT_OR_STRING: if typ2 not in [ BaseType.INT, BaseType.STRING, MetaType.INT_OR_STRING ]: return None return BaseType.INT if typ2 == MetaType.INT_OR_STRING else typ2 elif typ2 == MetaType.INT_OR_STRING: if typ1 not in [ BaseType.INT, BaseType.STRING, MetaType.INT_OR_STRING ]: return None return BaseType.INT if typ1 == MetaType.INT_OR_STRING else typ1 else: raise CompError('Internal error: %s, %s', typ1, typ2)
def require_func(self, expr, args): desc = ('Call %s at %s' % (expr.name, expr.location.start)) if isinstance( expr, ast.FunctionCall) else ('%s' % expr.op) optype = expr.optype ndecl, ncall = len(optype.args), len(args) if ndecl != ncall: diff = 'too %s parameters (saw %d, require %d)' % ( 'many' if ndecl < ncall else 'few', ncall, ndecl) raise CompError('%s has %s', desc, diff) sametypes = [] for decltype, callexpr in zip(optype.args, args): if decltype == MetaType.SAME_TYPE: sametypes.append(callexpr) else: callexpr.typ = self.require(callexpr, decltype) # Fake polymorphism # This doesn't work anymore, we can compare string expr vs string const # TODO: Make a table? if len(sametypes): # Just == and != for now if len(sametypes) != 2: raise CompError('Internal error: %s, %s', expr, args) s1, s2 = sametypes # pylint: disable=unbalanced-tuple-unpacking resolved = None if s1.typ == MetaType.INT_OR_STRING or s2.typ == MetaType.INT_OR_STRING: # May return None resolved = self.resolve_intstring(s1.typ, s2.typ) elif s1.typ == s2.typ: resolved = s1.typ if resolved is None: raise CompError( '%s needs two compatible types; found %s and %s', desc, s1.typ, s2.typ) s1.typ = s2.typ = resolved return optype.ret
def gen_target(self, expr): """ Returns the target (variable or constant) corresponding to an expression If no target exists, a temporary variable is generated. No CSE is performed at this stage. """ if isinstance(expr, VarExpr): # Should use declnum to distinguish scoped things # Useful for loops and stuff later if hasattr(expr.decl, 'persist'): return PersistVariable(expr.decl.persist, expr.var, expr.location) else: return Variable(expr.var, expr.location, 0) elif isinstance(expr, FlagExpr): return FlagVariable(expr.var, expr.location) elif isinstance(expr, ConstExpr): if isinstance(expr.value, StringExpr): return self.gen_pieces(expr.value.pieces) # TODO: Does this case happen? if isinstance(expr.value, str): return StringConstant(expr.value) return Constant(int(expr.value)) elif isinstance(expr, BinaryExpr): # This shares a common routine with expr tmp = self.gen_temp() self.gen_assignment(expr, tmp) return tmp elif isinstance(expr, UnaryExpr): if isinstance(expr.arg, ConstExpr): if expr.op == UnaryOp.NOT: return Constant(int(not expr.arg.value)) else: return Constant(-expr.arg.value) tmp = self.gen_temp() self.gen_assignment(expr, tmp) return tmp elif isinstance(expr, FunctionCall): tmp = self.gen_temp() self.gen_call(expr, tmp) return tmp else: raise CompError('Unknown expression %s', expr)
def previsit_statement(self, stmt, parent, prev_stmt): stmt.defined = parent.defined if prev_stmt is not None: stmt.defined = prev_stmt.defined stmt.persist = parent.persist # TODO: Check ~ LiteralStmt actually calls a command, rather than whitespace if isinstance(stmt, ast.AssignStmt): # TODO: Remove this once registers.py is fully implemented. if re.match(TMPVAR_RE, stmt.lhs.var): raise CompError( 'Invalid variable name %s (tmp plus numbers), ' 'until full IR renaming exists', stmt.lhs) stmt.defined = stmt.defined.copy() stmt.lhs.typ = BaseType.INT prevdecl = stmt.defined.get(stmt.lhs.var) if prevdecl is not None and hasattr(prevdecl, 'persist'): stmt.lhs.persist = prevdecl.persist # TODO: Move this to visiting the expression. ?? stmt.defined[stmt.lhs.var] = stmt.lhs elif isinstance(stmt, ast.LiteralStmt) and stmt.op == LiteralOp.CALL: pass
def gen_conditional(self, expr, negate=False): """ This is a specialization of gen_target when dealing with conditional expressions (i.e., ones used for branching). Emits a chain of blocks necessary to evaluate the conditional expression, the last of which will be a new block which is only reached if all of these blocks are successfully successfully passed through. When this function returns, the current block will be what happens if the overall condition is true, and the return value will be the list of blocks which need to be given a false branch if the overall condition is false at any stage. """ # If a BooleanExpr, we are not generating CondInstrs, only gluing them together. # TODO: Rewrite all of this probably. Currently unreachable due to AST limits. if isinstance(expr, BinaryExpr) and expr.op in boolean_ops: # Tricky bit: if negating the expression, use the other kind of BinaryOp # and negate both lhs and rhs. This is just DeMorgan's law. # Note: negate is not currently used, but was previously, and doesn't add # much complexity. if (expr.op == BinaryOp.AND) != negate: #if (lhs && rhs) then [true branch] else [false branch] #if lhs then (if rhs then [true branch] else [false branch]) else [false branch] lblocks = self.gen_conditional(expr.lhs, negate) rblocks = self.gen_conditional(expr.rhs, negate) # If a block in lhs or in rhs is false, the overall expression will be false. false_blocks = lblocks + rblocks elif (expr.op == BinaryOp.OR) != negate: #if (lhs || rhs) then [true branch] else [false branch] #if lhs then [true branch] else (if rhs then [true branch] else [false branch]) lblocks = self.gen_conditional(expr.lhs, negate) empty_true_block = self.current_block() rhs_block = self.new_block() rblocks = self.gen_conditional(expr.rhs, negate) true_block = self.current_block() # If a block in lhs is false, the overall expression depends on the rhs. for block in lblocks: block.add_successor(rhs_block) # If all blocks in lhs are true, the overall expression will be true. # This requires an empty block, but it'll be merged. empty_true_block.add_successor(true_block) # If a block in rhs is false, the overall expression will be false. false_blocks = rblocks return false_blocks else: if isinstance(expr, BinaryExpr) and expr.op in comparison_ops: op = expr.op args = [expr.lhs, expr.rhs] elif isinstance(expr, FunctionCall) and expr.name == 'isint': op = 'isint' args = [expr.args[0]] else: raise CompError('Cannot do comparison %s', expr) op = opposite_comparison[op] if negate else op target_args = [self.gen_target(arg) for arg in args] self.append(CondInstr(op, target_args)) cond_block = self.current_block() true_block = self.new_block() cond_block.add_successor(true_block) return [cond_block]
def gen_call(self, expr, target): optype = expr.optype args = expr.args if target is None and optype.name != 'sleep': # Could allow this, but it is a code smell as well raise CompError('No assigned target for %s', expr) if optype.name == 'persist': # All persist variables are already marked # TODO: Make sure they are... return # If it's being called from here, it's outside of the context of # a conditional - it's being assigned to a variable. elif optype.name == 'isint': # Some duplication with gen_conditional, but we do not # need multiple blocks. # First, assume it's false arg_target = self.gen_target(args[0]) self.append(MoveInstr(Constant(int(False)), target)) self.append(CondInstr('isint', [arg_target])) cond_block = self.current_block() # Set it to true in the true block true_block = self.new_block(cond_block) self.append(MoveInstr(Constant(int(True)), target)) # Then, everything goes to the end block. self.new_block(true_block, cond_block) elif optype.name == 'parseint': # For now, explicit quit. # BSL seems to do something similar. arg_target = self.gen_target(args[0]) self.append(CondInstr('isstring', [arg_target])) cond_block = self.current_block() # Panic in the true block true_block = self.new_block(cond_block) self.exits.append(true_block) # Otherwise, go to end block. self.new_block(cond_block) self.append(MoveInstr(arg_target, target)) else: # The rest are just normal FuncInstrs if optype.name == 'rand': func_name = 'rng' targets = [args[0].lhs, args[0].rhs] elif optype.name == 'sleep': func_name = 'slp' targets = [args[0]] elif optype.name == 'time': func_name = 'tme' targets = [] elif optype.name == 'hash': func_name = 'fpt' # Last arg[2] should either be int constant or string constant. # And, do transformation here. if isinstance(args[2].value, int): seed = args[2].value else: s = self.extract_string(args[2]) seed = hashimpl.murmurhash3_32(s) targets = [args[1].lhs, args[1].rhs, args[0], ConstExpr(seed)] else: raise CompError('Unknown function in %s', expr) arg_targets = [] for arg in targets: arg_target = self.gen_target(arg) arg_targets.append(arg_target) self.append(FuncInstr(func_name, arg_targets, target))
def gen_assignment(self, expr, target): """ After these instructions are emitted, target will be assigned to the value of the expression. Target must be a Variable or TmpVar. """ if isinstance(expr, (VarExpr, FlagExpr, ConstExpr)): # TODO Special case: persist calls var = self.gen_target(expr) self.append(MoveInstr(var, target)) elif isinstance(expr, BinaryExpr): # TODO: Add a negate argument here and use it here # This section should NOT call gen_assignment on expr, only subexprs # Because gen_target calls this if expr.op in [BinaryOp.AND, BinaryOp.OR]: stor = target # Boolean expressions are normally generated as: # z = x && y: [ z = x; if (z != 0) z = y; ] # This does not work when z is modified in x, so we use a temporary instead. # For, example: x = 0; x = (y = x++) || x; # Make sure this works for static variables - reason that this is fine is # that we can have some global x for the above example, so we need to do the same # assign to temp and store back in location # ... Don't do this? #if (isinstance(target, Variable) or isinstance(target, StaticVariable)) and target.name in expr.assigns_vars: # stor = self.gen_temp() self.gen_assignment(expr.lhs, stor) # Skip over the secondary instruction if neccesary if expr.op == BinaryOp.AND: #if x != 0 then y else x self.append(CondInstr(BinaryOp.NOTEQUAL, [stor, CondInstr.ZERO])) elif expr.op == BinaryOp.OR: #if x == 0 then y else x self.append(CondInstr(BinaryOp.ISEQUAL, [stor, CondInstr.ZERO])) block = self.current_block() # in case the lhs generated new blocks noshort = self.new_block() block.add_successor(noshort) # This is the fallthrough (non-short-circuit) branch # We gen another assignment because we want the result to be in the same # location as that assignment, because the result is in that location self.gen_assignment(expr.rhs, stor) # And now the short-circuit and non-short-circuit branches point to the # block after noshort_end = self.current_block() after = self.new_block() block.add_successor(after) noshort_end.add_successor(after) if stor is not target: self.append(MoveInstr(stor, target)) elif expr.op == BinaryOp.RANGE: raise CompError('Cannot generate assignment for %s', expr) else: t1 = self.gen_target(expr.lhs) t2 = self.gen_target(expr.rhs) self.append(OpInstr(expr.op, target, [t1, t2])) elif isinstance(expr, UnaryExpr): # Unary expressions are a convenience of the language that get wiped before BSL. if isinstance(expr.arg, ConstExpr): if expr.op == UnaryOp.NOT: value = int(not expr.arg.value) else: value = -expr.arg.value self.append(MoveInstr(value, target)) elif expr.op == UnaryOp.NOT: t1 = self.gen_target(expr.arg) self.append(OpInstr(BinaryOp.MINUS, target, [Constant(1), t1])) else: t1 = self.gen_target(expr.arg) self.append(OpInstr(BinaryOp.MINUS, target, [Constant(0), t1])) elif isinstance(expr, FunctionCall): self.gen_call(expr, target) else: raise CompError('Unknown expression %s', expr)
def gen_statement(self, stmt): if isinstance(stmt, ConditionalStmt): # hello self.gen_if(stmt.condition, stmt.truestmt, stmt.falsestmt) elif isinstance(stmt, LoopStmt): self.gen_loop(stmt.condition, stmt.statements) elif isinstance(stmt, AssignStmt): if isinstance(stmt.lhs, VarExpr): var = self.gen_target(stmt.lhs) self.gen_assignment(stmt.rhs, var) elif isinstance(stmt, FunctionStmt): self.gen_call(stmt.function, None) elif isinstance(stmt, PostStmt): var = self.gen_target(stmt.arg) self.gen_incdec(stmt.incdec, var) elif isinstance(stmt, SwitchStmt): var = self.gen_target(stmt.arg) # List of (list of (op, value), statements) cases = [] default = None # TODO: Optimize int better. Don't need bottom range if # all ranges are adjacent, and can do binary search even for large # numbers of cases, given 50 instruction limit. if stmt.typ not in [BaseType.INT, BaseType.STRING]: raise CompInternalError(stmt) for case in stmt.cases: anyof = [] if not case.vals: default = case.statements continue for val in case.vals: if stmt.typ == BaseType.STRING: strval = self.extract_string(val) anyof.append((BinaryOp.ISEQUAL, strval)) else: if isinstance(val, ConstExpr): anyof.append((BinaryOp.ISEQUAL, int(val.value))) elif isinstance(val, BinaryExpr) and val.op == BinaryOp.RANGE: anyof.append((BinaryOp.INRANGE, (int(val.lhs.value), int(val.rhs.value)))) else: raise CompInternalError(stmt) cases.append((anyof, case.statements)) # Generate all cases, then generate all statements # There are some weird inverted conditions here, mainly used so that # the expected branch is false, so it occurs directly after. # List of (list of blocks going to statements when true, statements) statement_blocks = [] for conds, statements in cases: # Blocks going to statement when true case_statement_blocks = [] for op, val in conds: # If any blocks are false, go to the next cond. # If all blocks are true, go to statements. if op == BinaryOp.ISEQUAL: const = StringConstant(val) if isinstance(val, str) else Constant(val) self.append(CondInstr(BinaryOp.ISEQUAL, [var, const])) check_block = self.current_block() self.new_block(check_block, branch=False) case_statement_blocks.append(check_block) elif op == BinaryOp.INRANGE: bottom, top = val # If out of bottom range, go to next cond. Else, check upper count. # TODO: This is optional if the ranges are adjacent. self.append(CondInstr(BinaryOp.GREATERTHAN, [var, Constant(top)])) first_check = self.current_block() second_check = self.new_block(first_check, branch=False) self.append(CondInstr(BinaryOp.GREATEREQ, [var, Constant(bottom)])) next_block = self.new_block(second_check, branch=False) first_check.add_successor(next_block, branch=True) case_statement_blocks.append(second_check) statement_blocks.append((case_statement_blocks, statements)) end_blocks = [] if default: # If no other conditions get met, do the default action. self.gen_statements(default) # The default action (even if it's nothing) goes to the end. end_blocks.append(self.current_block()) for blocks, statements in statement_blocks: self.new_block(*blocks, branch=True) self.gen_statements(statements) end_blocks.append(self.current_block()) # New block where everything combines self.new_block(*end_blocks) #collections.namedtuple('SwitchStmt', ['arg', 'cases']), #collections.namedtuple('CaseStmt', ['statements', 'vals']), #collections.namedtuple('BinaryExpr', ['op', 'lhs', 'rhs']), elif isinstance(stmt, LiteralStmt): pieces = self.gen_pieces(stmt.pieces) self.append(LiteralInstr(stmt.op, pieces)) elif isinstance(stmt, QuitStmt): # TODO: Should check that there aren't unreachable statements. self.exits.append(self.current_block()) self.new_block() else: raise CompError('Unknown statement %s', stmt)
def __init__(self, expr, typ): # These errors could be more descriptive, but one-size-fits-all is good for now. CompError.__init__(self, 'Expression at %s has type %s; expected %s', expr.location, expr.typ, typ) self.expr = expr self.typ = typ
def __init__(self, call): CompError.__init__(self, "Function %s called at %s does not exist", call.name, call.location.start) self.call = call
def __init__(self, var, location): CompError.__init__( self, "Variable %s at %s used without being defined first", var, location.start) self.var = var self.location = location
def __init__(self, call): CompError.__init__( self, "persist(\"%s\") call at %s not assigned directly to a variable", call.args[0], call.location.start) self.call = call