void compile(Scanner &input, Output &output, Opt &opts)
{
    RulesBlocks rblocks;
    const conopt_t *globopts = &opts.glob;
    code_alc_t &alc = output.allocator;
    const loc_t &loc0 = input.tok_loc();
    std::string block_name;
    loc_t block_loc;

    output.header_mode(1);
    output.new_block(opts, INPUT_GLOBAL, block_name, loc0);
    output.wversion_time();

    output.header_mode(0);
    output.new_block(opts, INPUT_GLOBAL, block_name, loc0);
    output.wversion_time();
    output.wdelay_stmt(0, code_line_info_input(alc, loc0));

    if (globopts->target == TARGET_SKELETON) {
        output.wdelay_stmt(0, emit_skeleton_prolog(output));
    }

    const opt_t *accum_opts = output.block().opts;

    for (;;) {
        // parse everything up to the next re2c block
        InputBlockKind kind = input.echo(output, block_name);
        if (kind == INPUT_ERROR) exit(1);
        if (kind == INPUT_END) break;

        // parse the next re2c block
        specs_t specs;
        if (kind == INPUT_USE) {
            const RulesBlock *rb = rblocks.find(block_name);
            if (rb == NULL) exit(1);
            specs = rb->specs;
            opts.restore(rb->opts);
            output.state_goto = false;
        }
        output.cond_goto = false;
        block_loc = input.tok_loc();
        parse(input, specs, opts, rblocks);

        // start new output block with accumulated options
        output.new_block(opts, kind, block_name, block_loc);

        if (kind == INPUT_RULES) {
            // save AST and options for future use
            rblocks.add(block_name, output.block().opts, specs);
        } else {
            check_and_merge_special_rules(specs, output.block().opts, output.msg);

            // compile AST to DFA
            dfas_t dfas;
            for (specs_t::const_iterator i = specs.begin(); i != specs.end(); ++i) {
                dfas.push_back(ast_to_dfa(*i, output));
            }

            // compile DFA to code
            gen_code(output, dfas);
        }
        output.wdelay_stmt(0, code_line_info_input(alc, input.cur_loc()));

        // accumulate whole-program information from this block
        output.gather_info_from_block();

        // Do not accumulate whole-program options for rules/reuse/local blocks.
        // Global blocks add their named definitions and configurations to the
        // global scope, local blocks don't. Historically global is the default.
        if (kind == INPUT_GLOBAL) {
            accum_opts = output.block().opts;
        } else {
            opts.restore(accum_opts);
        }
    }

    output.total_opts = accum_opts ? accum_opts : rblocks.last_opts();

    // For special targets (skeleton and .dot) merge header into the output file.
    if (globopts->target != TARGET_CODE && output.need_header) {
        output.need_header = false;
        blocks_t &cblocks = output.cblocks, &hblocks = output.hblocks;
        cblocks.insert(cblocks.end(), hblocks.begin(), hblocks.end());
        hblocks.clear();
    }

    if (globopts->target == TARGET_SKELETON) {
        output.wdelay_stmt(0, emit_skeleton_epilog(output));
    }

    AST::flist.clear();
    SemAct::flist.clear();
    RangeSuffix::freeList.clear();
}