""" module_names = ['bar', 'qux'] tests = [t0, t1, t2, t3, t4, t5, t6, t7, t8, t9] msgs = [m0, m1, m2, m3, m4, m5, m6, m7, m8, m9] err_cnt = 0 for ind in range(len(tests)): test = tests[ind] expected_msg = msgs[ind].strip() # Forget the current NodeError state, if any, prior to # each test. # NodeError.reset() NodeError.LAST_N = 20 t = ast.parse(test, mode='exec').body[0] print('---- ---- ---- ----') scope_check(t, module_names=module_names) actual_msg = ('\n'.join(NodeError.LAST_MSGS)).strip() if expected_msg != actual_msg: print('ERROR in test %d' % ind) print('Expected:\n%s' % expected_msg) print('Got:\n%s' % actual_msg) err_cnt += 1 if err_cnt: print('%s FAILED' % sys.argv[0])
def compile_function(filename, main_name=None, toplevel_bindings=None, saveOutput=False, intermediate_output=None): NodeError.reset() print('\n\nCOMPILING [%s] main %s' % (filename, main_name if main_name else '(default)')) # Use whether intermediate_output is None to decide # whether to call printout blocks at all # Old code set intermediate_output to /dev/null if intermediate_output: try: intermediate_fout = open(intermediate_output, 'w') except BaseException as exc: NodeError.fatal_msg(None, ('cannot save intermediate output in [%s]' % intermediate_output)) else: intermediate_fout = None # Process imports in the input file, and find the main. # If there's no main, then bail out right away. try: rel_path = os.path.relpath(filename) filename = rel_path except Exception as e: # If that wasn't a good path, give up immediately NodeError.error_msg( None, "Failed to make relpath from %s: %s" % (filename, e)) NodeError.halt_on_error() print('%s: CALLING IMPORTER' % datetime.now()) importer = NameSpaces(filename, main_name) if not importer.qglmain: NodeError.fatal_msg(None, 'no qglmain function found') NodeError.halt_on_error() ptree = importer.qglmain if intermediate_output: ast_text_orig = pyqgl2.ast_util.ast2str(ptree) print(('%s: ORIGINAL CODE:\n%s' % (datetime.now(), ast_text_orig)), file=intermediate_fout, flush=True) # When QGL2 flattens various kinds of control flow and runtime # computations it emits QGL1 instruction that the user may not # have imported. # # TODO: this is a hack, but the approach of adding these # blindly to the namespace is also a hack. This is a # placeholder until we figure out a cleaner approach. required_imports = [ 'Wait', 'Barrier', 'Goto', 'LoadCmp', 'CmpEq', 'CmpNeq', 'CmpGt', 'CmpLt', 'BlockLabel', 'Store' ] modname = ptree.qgl_fname for symbol in required_imports: if not add_import_from_as(importer, modname, 'qgl2.qgl1', symbol): NodeError.error_msg(ptree, 'Could not import %s' % symbol) NodeError.halt_on_error() ptree1 = ptree # We may need to iterate over the inlining processes a few times, # because inlining may expose new things to inline. # # TODO: as a stopgap, we're going to limit iterations to 20, which # is enough to handle fairly deeply-nested, complex non-recursive # programs. What we do is iterate until we converge (the outcome # stops changing) or we hit this limit. We should attempt at this # point to prove that the expansion is divergent, but we don't # do this, but instead assume the worst if the program is complex # enough to look like it's "probably" divergent. # print('%s: CALLING INLINER' % datetime.now()) MAX_ITERS = 20 for iteration in range(MAX_ITERS): print('%s: ITERATION %d' % (datetime.now(), iteration)) inliner = Inliner(importer) ptree1 = inliner.inline_function(ptree1) NodeError.halt_on_error() if intermediate_output: print(('INLINED CODE (iteration %d):\n%s' % (iteration, pyqgl2.ast_util.ast2str(ptree1))), file=intermediate_fout, flush=True) if inliner.change_cnt == 0: NodeError.diag_msg( None, ('expansion converged after iteration %d' % iteration)) break if iteration == (MAX_ITERS - 1): NodeError.error_msg( None, ('expansion did not converge after %d iterations' % MAX_ITERS)) # transform passed toplevel_bindings into a local_context dictionary # FIXME: If the qgl2main provides a default for an arg # that is 'missing', then don't count it as missing arg_names = [x.arg for x in ptree1.args.args] if isinstance(toplevel_bindings, tuple): if len(arg_names) != len(toplevel_bindings): NodeError.error_msg( None, 'Invalid number of arguments supplied to qgl2main (got %d, expected %d)' % (len(toplevel_bindings), len(arg_names))) local_context = { name: quickcopy(value) for name, value in zip(arg_names, toplevel_bindings) } elif isinstance(toplevel_bindings, dict): invalid_args = toplevel_bindings.keys() - arg_names if len(invalid_args) > 0: NodeError.error_msg( None, 'Invalid arguments supplied to qgl2main: {}'.format( invalid_args)) missing_args = arg_names - toplevel_bindings.keys() if len(missing_args) > 0: NodeError.error_msg( None, 'Missing arguments for qgl2main: {}'.format(missing_args)) local_context = quickcopy(toplevel_bindings) elif toplevel_bindings: NodeError.error_msg( None, 'Unrecognized type for toplevel_bindings: {}'.format( type(toplevel_bindings))) else: local_context = None NodeError.halt_on_error() evaluator = EvalTransformer(SimpleEvaluator(importer, local_context)) print('%s: CALLING EVALUATOR' % datetime.now()) ptree1 = evaluator.visit(ptree1) NodeError.halt_on_error() if DebugMsg.ACTIVE_LEVEL < 3: print('%s: EVALUATOR RESULT:\n%s' % (datetime.now(), pyqgl2.ast_util.ast2str(ptree1))) # It's very hard to read the intermediate form, before the # QBIT names are added, so we don't save this right now. # print(('EVALUATOR RESULT:\n%s' % pyqgl2.ast_util.ast2str(ptree1)), # file=intermediate_fout, flush=True) # Dump out all the variable bindings, for debugging purposes # # print('EV total state:') # evaluator.print_state() evaluator.replace_bindings(ptree1.body) if DebugMsg.ACTIVE_LEVEL < 3: print('%s: EVALUATOR REBINDINGS:\n%s' % (datetime.now(), pyqgl2.ast_util.ast2str(ptree1))) if intermediate_output: print( ('EVALUATOR + REBINDINGS:\n%s' % pyqgl2.ast_util.ast2str(ptree1)), file=intermediate_fout, flush=True) # base_namespace = importer.path2namespace[filename] # if intermediate_output: # text = base_namespace.pretty_print() # print(('EXPANDED NAMESPACE:\n%s' % text), # file=intermediate_fout, flush=True) new_ptree1 = ptree1 # Try to flatten out repeat, range, ifs flattener = Flattener() print('%s: CALLING FLATTENER' % datetime.now()) new_ptree2 = flattener.visit(new_ptree1) NodeError.halt_on_error() if intermediate_output: print(('%s: FLATTENED CODE:\n%s' % (datetime.now(), pyqgl2.ast_util.ast2str(new_ptree2))), file=intermediate_fout, flush=True) # TODO Is it ever necessary to replace bindings again at this point? # evaluator.replace_bindings(new_ptree2.body) # evaluator.get_state() if intermediate_output: print(('Final qglmain: %s\n' % new_ptree2.name), file=intermediate_fout, flush=True) new_ptree3 = new_ptree2 # Done. Time to generate the QGL1 # Try to guess the proper function name fname = main_name if not fname: if isinstance(ptree, ast.FunctionDef): fname = ptree.name else: fname = "qgl1Main" # Get the QGL1 function that produces the proper sequences print('%s: GENERATING QGL1 SEQUENCE FUNCTION' % datetime.now()) qgl1_main = get_sequence_function(new_ptree3, fname, importer, evaluator.allocated_qbits, intermediate_fout, saveOutput, filename, setup=evaluator.setup()) NodeError.halt_on_error() return qgl1_main