예제 #1
0
    def add_type_binding(self, node, name, name_type):
        """
        Add a binding between a name and a type, in the local context.

        Gripe and do nothing if there is already a binding for this
        name in either the parameter or local scope, and it disagrees
        with the requested binding.

        The node parameter is used only to generate error messages
        that can be traced back to the original code, since the node
        contains the file and line number of the code prior to
        any transformation
        """

        if name in self.parameter_names:
            old_type = self.parameter_names[name]
            if old_type != name_type:
                NodeError.error_msg(node, ('parameter type changed %s -> %s' %
                                           (old_type, name_type)))
        elif name in self.local_names:
            old_type = self.local_names[name]
            if old_type != name_type:
                NodeError.error_msg(
                    node, 'type changed %s -> %s' % (old_type, name_type))
        else:
            NodeError.diag_msg(node, 'add type %s -> %s' % (name, name_type))
            self.local_names[name] = name_type
예제 #2
0
파일: single.py 프로젝트: stjordanis/pyqgl2
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
예제 #3
0
파일: single.py 프로젝트: stjordanis/pyqgl2
    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
예제 #4
0
def get_sequence_function(node,
                          func_name,
                          importer,
                          allocated_qregs,
                          intermediate_fout=None,
                          saveOutput=False,
                          filename=None,
                          setup=None):
    """
    Create a function that encapsulates the QGL code
    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 = SequenceExtractor(importer, allocated_qregs)

    builder.find_sequences(node)
    builder.find_imports(node)
    code = builder.emit_function(func_name, setup)
    if intermediate_fout:
        print(('#start function\n%s\n#end function' % code),
              file=intermediate_fout,
              flush=True)
    if saveOutput and filename:
        newf = os.path.abspath(filename[:-3] + "qgl1.py")
        with open(newf, 'w') as compiledFile:
            compiledFile.write(code)
        print("Saved compiled code to %s" % newf)

    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)
    NodeError.halt_on_error()

    return scratch_scope[func_name]
예제 #5
0
    def concur_wait(self, node):
        """
        Synchronize the start of each seq block within a concur block,

        Add seq blocks for any "missing" channels so we can
        add a Barrier instruction for each of them as well
        """
        global BARRIER_CTR

        # This method will be destructive, unless we make a new
        # copy of the AST tree first
        #
        node = deepcopy(node)

        seen_channels = set()

        # Channels in this with_concur
        concur_channels = find_all_channels(node)

        # For creating the Barriers, we want QGL1 scoped variables that will be real channel instances.
        # We basically have that already.
        real_chans = set()
        for chan in concur_channels:
            real_chans.add(chan)

        start_barrier = BARRIER_CTR
        end_barrier = start_barrier + 1
        BARRIER_CTR += 2

        for stmnt in node.body:
            if not is_seq(stmnt):
                NodeError.error_msg(stmnt,
                                    'non-seq block inside concur block?')
                return node

            seq_channels = find_all_channels(stmnt)

            if seq_channels.intersection(seen_channels):
                NodeError.error_msg(stmnt,
                                    'seq blocks have overlapping channels')
                return node

            seen_channels = seen_channels.union(seq_channels)

            chan_name = ','.join(seq_channels)

            # mark stmnt with chan_name or seq_channels in another way
            if hasattr(stmnt, 'qgl_chan_list'):
                oldChanSet = set(stmnt.qgl_chan_list)
                newChanSet = seq_channels
                oldMissing = newChanSet - oldChanSet
                oldExtra = oldChanSet - newChanSet
                if len(oldMissing) > 0:
                    NodeError.diag_msg(
                        stmnt, 'marked chan list %s was missing %s' %
                        (str(oldChanSet), str(oldMissing)))
                if len(oldExtra) > 0:
                    NodeError.diag_msg(
                        stmnt, 'marked chan list %s had extra %s' %
                        (str(oldChanSet), str(oldExtra)))
            NodeError.diag_msg(stmnt,
                               'Marking chan list %s' % (str(seq_channels)))
            stmnt.qgl_chan_list = list(seq_channels)

            new_seq_body = list()

            # Helper to ensure the string we feed to AST doesn't put quotes around
            # our Qubit variable names
            def appendChans(bString, chans):
                bString += '['
                first = True
                for chan in chans:
                    if first:
                        bString += str(chan)
                        first = False
                    else:
                        bString += "," + str(chan)
                bString += ']'
                return bString

            # Add global ctr, chanlist=concur_channels
            # FIXME: Hold concur_channels as a string? List?
            bstring = 'Barrier("%s", ' % str(start_barrier)
            bstring = appendChans(bstring, list(real_chans))
            bstring += ')\n'
            barrier_ast = expr2ast(bstring)
            # barrier_ast = expr2ast('Barrier(%s, %s)\n' % (str(start_barrier), list(real_chans)))
            copy_all_loc(barrier_ast, node)
            barrier_ast.channels = concur_channels
            # print("*****Start barrier: %s" % pyqgl2.ast_util.ast2str(barrier_ast))

            new_seq_body.append(barrier_ast)

            new_seq_body += stmnt.body

            bstring = 'Barrier("%s", ' % str(end_barrier)
            bstring = appendChans(bstring, list(real_chans))
            bstring += ')\n'
            end_barrier_ast = expr2ast(bstring)
            #end_barrier_ast = expr2ast('Barrier(%s, %s)\n' % (str(end_barrier), list(real_chans)))
            copy_all_loc(end_barrier_ast, node)
            # Add global ctr, chanlist=concur_channels
            end_barrier_ast.channels = concur_channels

            # print('End AST: %s' % ast2str(end_barrier_ast))

            new_seq_body.append(end_barrier_ast)

            stmnt.body = new_seq_body

        # FIXME: In new thinking, is the proper unseen set the global one,
        # Or only those local to this with concur. I think only local
        for unseen_chan in concur_channels - seen_channels:
            #print('DIAG %s' % ast2str(stmnt))
            NodeError.diag_msg(
                stmnt,
                'channels unreferenced in concur: %s' % str(unseen_chan))

            bstring = 'with seq:\n    Barrier("%s", ' % str(start_barrier)
            bstring = appendChans(bstring, list(real_chans))
            bstring += ')\n    Barrier("%s",' % str(end_barrier)
            bstring = appendChans(bstring, list(real_chans))
            bstring += ')\n'
            empty_seq_ast = expr2ast(bstring)
            # print('Empty AST: %s' % ast2str(empty_seq_ast))
            # empty_seq_ast = expr2ast(
            #         'with seq:\n    Barrier(%s, %s)\n    Barrier(%s, %s)' % (str(start_barrier), list(real_chans), str(end_barrier), list(real_chans)))

            # Mark empty_seq_ast with unseen_chan
            empty_seq_ast.qgl_chan_list = [unseen_chan]
            copy_all_loc(empty_seq_ast, node)
            node.body.append(empty_seq_ast)

        return node
예제 #6
0
    def __init__(self, path, qglmain_name=None, text=None):

        # map from path to AST
        #
        self.path2ast = dict()

        # map from path to NameSpace
        #
        self.path2namespace = dict()

        # The error/warning messages are clearer if we always
        # use the relpath
        #
        if not path or path == '<stdin>':
            self.base_fname = '<stdin>'
        else:
            self.base_fname = os.path.relpath(path)

        # Reference to the main function; initially None because
        # we haven't read it in yet
        #
        self.qglmain = None

        if text:
            self.read_import_str(text, self.base_fname)
        else:
            self.read_import(self.base_fname)

        # TODO: if the user asks for a specific main, then go
        # back and use it.  Don't gripe if the user has already defined
        # one.  Resolve the name with respect to the namespace
        # of base_fname

        if qglmain_name:
            qglmain_def = self.resolve_sym(self.base_fname, qglmain_name)

            if not qglmain_def:
                NodeError.error_msg(
                    None, 'no definition for qglmain [%s]' % qglmain_name)
            elif not qglmain_def.qgl_func:
                NodeError.error_msg(
                    None, 'qglmain [%s] not declared QGL' % qglmain_name)
            else:
                self.qglmain = qglmain_def
                qglmain_def.qgl_main = True

        if self.qglmain:
            NodeError.diag_msg(None,
                               'using [%s] as qglmain' % self.qglmain.name)
        else:
            NodeError.warning_msg(None,
                                  'warning: no qglmain declared or chosen')

        # This is a hack to make sure that the base file
        # is read in as a "native import".  Since there isn't
        # an explicit "import" of this file anywhere, we don't
        # have an AST node that contains the code for this import.
        # We can't use None, because this importer uses this as
        # a sentinel value, so we use self.qgl2main.  This is
        # bogus -- we should make a fake node for this purpose
        # FIXME

        fin = open(self.base_fname, 'r')
        text = fin.read()
        fin.close()

        namespace = self.path2namespace[self.base_fname]
        namespace.native_import(text, self.qglmain)
예제 #7
0
    def add_from_as(self, namespace, module_name, stmnt):
        """
        Process a "from import as" statement (where the "as" is
        optional).

        The rules for how this works in Python are complicated,
        not particularly well specified (from the docs I can find),
        and more than we're attempting to do.  Here's what we do:

        Given a statement with one of the following forms:

            from X import A as Z
            from X import A, B
            from X import *

        X is referred to here as the module name, but it is not
        required (as of Python 3.4) to refer to a module; it may
        refer to a package, or (as of 3.4) a directory containing
        other packages or modules but is not itself a package.

        Note: it is not legal for A to be a compound thing, i.e.
        "from os import path.sep" is invalid syntax.

        After converting X from Python notation (including relative
        paths) into a file system path XP, we check to see whether
        it resolves to a module (with name XP + ".py"), or a package
        (with name XP + '/__init__.py') or a directory (with name XP).

        In Python 3.4/3.5, there is a new feature of being able
        to do a from-import of a module from a directory, i.e.

            from X import A

        where A is a module, rather than a symbol inside module or
        package X.  We DO NOT support this feature yet.

        """

        # setup the namespace for this module
        # NOTE: this is incomplete: it only sets up the specific
        # name, and may do so repeatedly.
        # TODO: We should only do this once
        # TODO: and we should import the entire namespace so that
        # local functions can access local definitions and
        # functions that are otherwise private
        #
        namespace.native_import(pyqgl2.ast_util.ast2str(stmnt), stmnt)

        namespace.add_from_as_stmnt(stmnt)

        # print('NX orig statement [%s]' % ast.dump(stmnt))
        # print('NX orig statement [%s]' %
        #         pyqgl2.ast_util.ast2str(stmnt).strip())

        # placeholder
        subpath = None

        if stmnt.level > 0:
            # Deal with relative imports: these have a level of 1
            # or higher
            #
            # Find the directory by peeling the last component off
            # of stmnt.qgl_fname and keeping the rest.
            #
            # Then append the right number of '..' components (level - 1)
            # to either look in the same directory, or a parent directory.
            # The resulting path is hideous and non-canonical, but we'll
            # fix that later.
            #
            # Finally, add the relative component (after translating it
            # from Python notation to path notation, and adding the
            # suffix).
            #
            dir_name = stmnt.qgl_fname.rpartition(os.sep)[0]

            # If the relative path is for a parent directory, add
            # the proper number of '..' components.  A single '.',
            # however, represents this directory.
            #
            if stmnt.level > 1:
                dir_name += os.sep + os.sep.join(['..'] * (stmnt.level - 1))

                # We're going to convert the entire path to a relative path
                # later, but doing it for the directory prefix makes things
                # more legible while debugging
                #
                dir_name = os.path.relpath(dir_name)

            # if there's a module name, prepare to test whether it's
            # a file or a directory.  If there's not then the dir_name
            # is the dpath, and there is no fpath
            #
            if module_name:
                mod_path = os.sep.join(module_name.split('.'))
                from_path = os.path.join(dir_name, mod_path)
            else:
                from_path = dir_name

            # Now figure out what kind of thing is at the end of that
            # path: a module, a package, or a directory:

            module_path = from_path + '.py'
            package_path = os.path.join(from_path, '__init__.py')
            dir_path = from_path

            if os.path.isfile(module_path):
                subpath = module_path
            elif os.path.isfile(package_path):
                subpath = package_path
            elif os.path.isdir(dir_path):
                subpath = from_path

            # Since we don't know what our own module name is,
            # we can't figure out the "full" name of the relatively
            # imported module.  FIXME
            #
            full_module_name = None
            NodeError.warning_msg(
                stmnt, ('cannot evaluate exprs in a relative import [%s]' %
                        module_name))

        else:
            # use normal resolution to find the location of module
            #
            subpath = resolve_path(module_name)
            full_module_name = module_name

        # There are a lot of reasons why we might not be able
        # to resolve a module name; it could be in a binary file
        # or a zip file, or obscured in some other way, so that
        # the ordinary Python interpreter can find it but we cannot.
        # So we can't treat this as an error, even though it might
        # be one.
        #
        if subpath is None:
            NodeError.diag_msg(stmnt, ('path to [%s%s] not found' %
                                       ('.' * stmnt.level, module_name)))
        elif is_system_file(subpath):
            NodeError.diag_msg(stmnt, ('import of [%s%s] ignored' %
                                       ('.' * stmnt.level, module_name)))
        else:
            self.read_import(subpath)

            for imp in stmnt.names:
                if imp.name == '*':
                    NodeError.warning_msg(
                        stmnt,
                        ('deprecated wildcard import from [%s]' % module_name))
                    self.add_from_wildcard(namespace, subpath, module_name)

                    if full_module_name:
                        namespace.native_import(
                            ('from %s import *' % full_module_name), stmnt)
                else:
                    namespace.add_from_as_path(subpath, imp.name, imp.asname)

                    if full_module_name:
                        symname = imp.name
                        if imp.asname:
                            symname += ' %s' % imp.asname
                        namespace.native_import(('from %s import %s' %
                                                 (full_module_name, symname)),
                                                stmnt)
예제 #8
0
    def add_func_decorators(self, module_name, node):

        # print('NNN module_name %s ofname %s' % (module_name, self.base_fname))

        qglmain = False
        qglfunc = False
        other_decorator = False
        qglstub = False  # A stub for a QGL1 function; check args but do not inline
        qglstub_import = False
        qglmeas = False  # A QGL measurement

        if node.decorator_list:
            for dec in node.decorator_list:
                # qglmain implies qglfunc, but it's permitted to
                # have both
                #
                if isinstance(dec, ast.Name) and (dec.id == QGL2.QMAIN):
                    qglfunc = True
                    qglmain = True
                elif isinstance(dec, ast.Name) and (dec.id == QGL2.QSTUB):
                    # A stub for a QGL1 function; check args but do not inline
                    qglfunc = True
                    qglstub = True
                    NodeError.warning_msg(
                        node, ('old-style stub for [%s]: no import info' %
                               node.name))
                elif (isinstance(dec, ast.Call)
                      and isinstance(dec.func, ast.Name)
                      and dec.func.id == QGL2.QSTUB):
                    qglfunc = True
                    qglstub = True
                    qglstub_import = self.find_stub_import(dec, node.name)
                elif (isinstance(dec, ast.Call)
                      and isinstance(dec.func, ast.Name)
                      and dec.func.id == QGL2.QMEAS):
                    qglfunc = True
                    qglstub = True
                    qglmeas = True
                    qglstub_import = self.find_stub_import(dec, node.name)

                elif isinstance(dec, ast.Name) and (dec.id == QGL2.QDECL):
                    qglfunc = True
                else:
                    other_decorator = True

            if qglmain and other_decorator:
                NodeError.warning_msg(
                    node, 'unrecognized decorator with %s' % QGL2.QMAIN)
            elif qglfunc and other_decorator:
                NodeError.warning_msg(
                    node, 'unrecognized decorator with %s' % QGL2.QDECL)

        node.qgl_func = qglfunc
        # A stub for a QGL1 function; check args but do not inline
        node.qgl_stub = qglstub
        node.qgl_meas = qglmeas
        node.qgl_main = qglmain
        node.qgl_stub_import = qglstub_import

        # Only assign the qglmain at the root of the namespace
        # if we're in the base file
        #
        if qglmain and (module_name == self.base_fname):
            if self.qglmain:
                omain = self.qglmain

                # This is not an error; optimized versions of
                # the qglmain can be added without error
                #
                NodeError.diag_msg(node,
                                   'more than one %s function' % QGL2.QMAIN)
                NodeError.diag_msg(
                    node, 'previously defined %s:%d:%d' %
                    (omain.qgl_fname, omain.lineno, omain.col_offset))
            else:
                NodeError.diag_msg(
                    node, '%s declared as %s' % (node.name, QGL2.QMAIN))
                self.qglmain = node
예제 #9
0
파일: main.py 프로젝트: DebasishMaji/pyqgl2
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