Ejemplo n.º 1
0
    def read_import(self, path):
        """
        Recursively read the imports from the module at the given path
        """

        # TODO: error/warning/diagnostics

        if path in self.path2ast:
            return self.path2ast[path]

        # TODO: this doesn't do anything graceful if the file
        # can't be opened, or doesn't exist, or anything else goes
        # wrong.  We just assume that Python will raise an exception
        # that includes a useful error message.  FIXME we should
        # be more proactive about making sure that the user
        # gets the info necessary to diagnose the problem.
        #

        try:
            fin = open(path, 'r')
            text = fin.read()
            fin.close()
        except BaseException as exc:
            NodeError.fatal_msg(None,
                                'cannot open [%s]: %s' % (path, str(exc)))
            return None

        try:
            return self.read_import_str(text, path)
        except BaseException as exc:
            NodeError.fatal_msg(
                None, 'failed to import [%s]: %s %s' % (path, type(exc), exc))
            return None
Ejemplo n.º 2
0
def single_sequence(node, func_name, importer, setup=None):
    """
    Create a function that encapsulates the QGL code (for a single
    sequence) from the given AST node, which is presumed to already
    be fully pre-processed.

    TODO: we don't test that the node is fully pre-processed.
    TODO: each step of the preprocessor should mark the nodes
    so that we know whether or not they've been processed.
    """

    builder = SingleSequence(importer)

    if builder.find_sequence(node) and builder.find_imports(node):
        code = builder.emit_function(func_name, setup=setup)

        NodeError.diag_msg(node,
                           'generated code:\n#start\n%s\n#end code' % code)

        # TODO: we might want to pass in elements of the local scope
        scratch_scope = dict()
        eval(compile(code, '<none>', mode='exec'), globals(), scratch_scope)

        return scratch_scope[func_name]
    else:
        NodeError.fatal_msg(node,
                            'find_sequence failed: not a single sequence')
        return None
Ejemplo n.º 3
0
    def visit_For(self, node):
        """
        Discover loop variables.

        TODO: this is incomplete; we just assume that loop variables
        are all classical.  We don't attempt to infer anything about the
        iterator.
        """

        for subnode in ast.walk(node.target):
            if isinstance(subnode, ast.Attribute):
                # This is a fatal error and we don't want to confuse
                # ourselves by trying to process the ast.Name
                # nodes beneath
                #
                name_text = pyqgl2.importer.collapse_name(subnode)
                NodeError.fatal_msg(subnode,
                                    ('loop var [%s] is not local' % name_text))

            elif isinstance(subnode, ast.Name):
                name = subnode.id

                # Warn the user if they're doing something that's
                # likely to provoke an error
                #
                if self.name_is_in_lscope(name):
                    NodeError.warning_msg(
                        subnode,
                        ('loop var [%s] hides sym in outer scope' % name))

                DebugMsg.log('FOR (%s)' % name)
                self.add_type_binding(subnode, name, QGL2.CLASSICAL)

        self.visit_body(node.body)
        self.visit_body(node.orelse)
Ejemplo n.º 4
0
    def find_sequence(self, node):

        if not isinstance(node, ast.FunctionDef):
            NodeError.fatal_msg(node, 'not a function definition')
            return False

        self.qbits = find_all_channels(node)

        if len(self.qbits) == 0:
            NodeError.error_msg(node, 'no channels found')
            return False
        else:
            NodeError.diag_msg(node, "Found channels %s" % self.qbits)

        lineNo = -1
        while lineNo + 1 < len(node.body):
            lineNo += 1
            # print("Line %d of %d" % (lineNo+1, len(node.body)))
            stmnt = node.body[lineNo]
            # print("Looking at stmnt %s" % stmnt)
            assignment = self.is_qbit_create(stmnt)
            if assignment:
                self.qbit_creates.append(assignment)
                continue
            elif is_concur(stmnt):
                # print("Found concur at line %d: %s" % (lineNo+1,stmnt))
                for s in stmnt.body:
                    if is_seq(s):
                        # print("Found with seq for qbits %s: %s" % (s.qgl_chan_list, ast2str(s)))
                        #print("With seq next at line %d: %s" % (lineNo+1,s))
                        if str(s.qgl_chan_list) not in self.sequences:
                            self.sequences[str(s.qgl_chan_list)] = list()
                        thisSeq = self.sequences[str(s.qgl_chan_list)]
                        # print("Append body %s" % s.body)
                        # for s2 in s.body:
                        #     print(ast2str(s2))
                        thisSeq += s.body
                        #print("lineNo now %d" % lineNo)
                    else:
                        NodeError.error_msg(
                            s, "Not seq next at line %d: %s" % (lineNo + 1, s))
            elif isinstance(stmnt, ast.Expr):
                if len(self.qbits) == 1:
                    # print("Append expr %s to sequence for %s" % (ast2str(stmnt), self.qbits))
                    if len(self.sequences) == 0:
                        self.sequences[list(self.qbits)[0]] = list()
                    self.sequences[list(self.qbits)[0]].append(stmnt)
                else:
                    NodeError.error_msg(
                        stmnt, 'orphan statement %s' % ast.dump(stmnt))
            else:
                NodeError.error_msg(stmnt,
                                    'orphan statement %s' % ast.dump(stmnt))
        # print("Seqs: %s" % self.sequences)
        return True
Ejemplo n.º 5
0
    def do_concur(self, body):

        for stmnt in body:
            if not is_seq(stmnt):
                # TODO: this error message is not helpful
                NodeError.fatal_msg(stmnt, 'expected a "with seq" block')
                return
            else:
                # TODO: The grouper should annotate the seq statement
                # so we don't have to find the qbits again.
                #
                qbits = find_all_channels(stmnt)
                if not qbits:
                    print('XXN body\n%s' % ast2str(stmnt))
                self.do_seq(qbits, stmnt.body)
Ejemplo n.º 6
0
    def find_sequences(self, node):
        '''
        Input AST node is the main function definition.
        Strips out QRegister creation statements and builds
        a list of corresponding Qubit creation statements.
        Converts calls on QRegisters into calls on Qubits.'''

        if not isinstance(node, ast.FunctionDef):
            NodeError.fatal_msg(node, 'not a function definition')
            return False

        self.qbits = self.qbits_from_qregs(self.allocated_qregs)
        for q in self.qbits:
            # Using QubitFactory here means the qubit must already exist
            # If did Channels.Qubit, we'd be creating it new; but it wouldn't be in the ChannelLibrary
            stmnt = ast.parse("QBIT_{0} = QubitFactory('q{0}')".format(q))
            self.qbit_creates.append(stmnt)

        lineNo = -1
        while lineNo + 1 < len(node.body):
            lineNo += 1
            # print("Line %d of %d" % (lineNo+1, len(node.body)))
            stmnt = node.body[lineNo]
            # print("Looking at stmnt %s" % stmnt)
            if is_qbit_create(stmnt):
                # drop it
                continue

            elif isinstance(stmnt, ast.Expr):
                # expand calls on QRegisters into calls on Qubits
                if (hasattr(stmnt, 'qgl2_type')
                        and (stmnt.qgl2_type == 'stub'
                             or stmnt.qgl2_type == 'measurement')):
                    new_stmnts = self.expand_qreg_call(stmnt)
                    self.sequence.extend(new_stmnts)
                else:
                    self.sequence.append(stmnt)
            else:
                NodeError.error_msg(stmnt,
                                    'orphan statement %s' % ast.dump(stmnt))

        # print("Seqs: %s" % self.sequences)
        if not self.sequence:
            NodeError.warning_msg(node, "No qubit operations discovered")
            return False

        return True
Ejemplo n.º 7
0
    def visit_With(self, node):
        """
        If the node is a "with concur", then add a sequence
        for each "with seq" block in its body, with a WAIT
        preamble and SYNC(?) postamble.

        All other kinds of "with" blocks cause an error.
        """

        if is_concur(node):
            self.do_concur(node.body)

            # TODO: if there's an orelse, or anything like
            # that, then gripe here.  We can't handle that yet.

        else:
            # TODO: this error message is not helpful
            NodeError.fatal_msg(node, 'Unexpected with block')
Ejemplo n.º 8
0
    def find_stub_import(self, decnode, funcname):
        """
        Find the import info encoded in a stub declaration

        TODO: doesn't do anything useful with errors/bad input
        """

        if not isinstance(decnode, ast.Call):
            NodeError.fatal_msg(
                decnode,
                'bad use of find_stub_import [%s]' % ast.dump(decnode))

        args = decnode.args
        n_args = len(args)

        from_name = None
        orig_name = None

        if n_args == 0:
            # TODO: should complain
            pass

        if n_args > 0:
            if not isinstance(args[0], ast.Str):
                NodeError.error_msg(
                    decnode,
                    'qgl2stub arg[0] must be str [%s]' % ast.dump(args[0]))
            else:
                from_name = args[0].s

        if n_args > 1:
            if not isinstance(args[1], ast.Str):
                NodeError.error_msg(
                    decnode,
                    'qgl2stub arg[1] must be str [%s]' % ast.dump(args[1]))
            else:
                orig_name = args[1].s

        if n_args > 2:
            # TODO: should complain
            pass

        return (funcname, from_name, orig_name)
Ejemplo n.º 9
0
    def visit_With(self, node):
        """
        TODO: this is incomplete; we just assume that with-as variables
        are all classical.  We don't attempt to infer anything about their
        type.  (This is likely to be true in most cases, however)
        """

        for item in node.items:
            if not item.optional_vars:
                continue

            for subnode in ast.walk(item.optional_vars):
                if isinstance(subnode, ast.Attribute):
                    # This is a fatal error and we don't want to confuse
                    # ourselves by trying to process the ast.Name
                    # nodes beneath
                    #
                    name_text = pyqgl2.importer.collapse_name(subnode)
                    NodeError.fatal_msg(
                        subnode, ('with-as var [%s] is not local' % name_text))

                elif isinstance(subnode, ast.Name):
                    name = subnode.id

                    DebugMsg.log('GOT WITH (%s)' % name)

                    # Warn the user if they're doing something that's
                    # likely to provoke an error
                    #
                    if self.name_is_in_lscope(name):
                        NodeError.warn_msg(
                            subnode,
                            ('with-as var [%s] hides sym in outer scope' %
                             name))
                    self.add_type_binding(subnode, subnode.id, QGL2.CLASSICAL)

        self.visit_body(node.body)
Ejemplo n.º 10
0
def find_sys_path_prefix():
    """
    Find the prefix of the path to the "system" libraries,
    which we want to exclude from importing and searching
    for QGL stuff.

    Where these are located depends on where Python was
    installed on the local system (and which version of
    Python, etc).  The heuristic we use is to search through
    the include path for 'ast' and assume that the path
    we has the prefix we want to omit.

    This is a hack.  In Python3, modules can be loaded
    directly out of zip files, in which case they don't
    have a "file".  We use 'ast' because it typically does,
    but there's no guarantee that this will work in
    all cases.
    """

    global SYS_PATH_PREFIX

    if SYS_PATH_PREFIX:
        return SYS_PATH_PREFIX

    try:
        path = inspect.getfile(ast)
    except TypeError as exc:
        NodeError.fatal_msg(None, 'cannot find path to system modules')

    relpath = os.path.relpath(path)

    path_prefix = relpath.rpartition(os.sep)[0]

    SYS_PATH_PREFIX = path_prefix

    return path_prefix
Ejemplo n.º 11
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