Constructing a suitable makefile for a simple inweb project.

§1. This section offers just one function, which constructs a makefile by following a "prototype".

typedef struct makefile_state {
    struct web *for_web;
    struct text_stream to_makefile;
    struct text_stream *repeat_block;  a "repeatblock" body being scanned
    int inside_block;  scanning a "repeatblock" into that text?
    int last_line_was_blank;  used to suppress runs of multiple blank lines
    int allow_commands;  permit the prototype to use special commands
    int repeat_scope;  during a repeat, either MAKEFILE_TOOL_MOM or MAKEFILE_MODULE_MOM
    struct text_stream *repeat_tag;
    struct dictionary *tools_dictionary;
    struct dictionary *webs_dictionary;
    struct dictionary *modules_dictionary;
    struct module_search *search_path;
} makefile_state;

void Makefiles::write(web *W, filename *prototype, filename *F, module_search *I) {
    makefile_state MS;
    MS.for_web = W;
    MS.last_line_was_blank = TRUE;
    MS.repeat_block = Str::new();
    MS.inside_block = FALSE;
    MS.allow_commands = TRUE;
    MS.tools_dictionary = Dictionaries::new(16, FALSE);
    MS.webs_dictionary = Dictionaries::new(16, FALSE);
    MS.modules_dictionary = Dictionaries::new(16, FALSE);
    MS.search_path = I;
    MS.repeat_scope = -1;
    MS.repeat_tag = NULL;
    text_stream *OUT = &(MS.to_makefile);
    if (STREAM_OPEN_TO_FILE(OUT, F, ISO_ENC) == FALSE)
        Errors::fatal_with_file("unable to write tangled file", F);
    WRITE("# This makefile was automatically written by inweb -makefile\n");
    WRITE("# and is not intended for human editing\n\n");
    TextFiles::read(prototype, FALSE, "can't open prototype file",
        TRUE, Makefiles::scan_makefile_line, NULL, &MS);
    STREAM_CLOSE(OUT);
    WRITE_TO(STDOUT, "Wrote makefile '%f' from script '%f'\n", F, prototype);
}

§2.

void Makefiles::scan_makefile_line(text_stream *line, text_file_position *tfp, void *X) {
    makefile_state *MS = (makefile_state *) X;
    text_stream *OUT = &(MS->to_makefile);

    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, line, L" *#%c*")) { Regexp::dispose_of(&mr); return; }  Skip comment lines
    if (MS->allow_commands) {
        if (Regexp::match(&mr, line, L" *{repeat-tools-block:(%C*)} *"))
            Begin a repeat tool block2.1;
        if (Regexp::match(&mr, line, L" *{repeat-webs-block:(%C*)} *"))
            Begin a repeat web block2.2;
        if (Regexp::match(&mr, line, L" *{repeat-modules-block:(%C*)} *"))
            Begin a repeat module block2.3;
        if (Regexp::match(&mr, line, L" *{end-block} *")) End a repeat block2.5;
        if (MS->inside_block) Deal with a line in a repeat block2.4;

        if (Regexp::match(&mr, line, L"(%c*){repeat-tools-span}(%c*?){end-span}(%c*)"))
            Deal with a repeat span2.6;
        if (Regexp::match(&mr, line, L"(%c*){repeat-webs-span}(%c*?){end-span}(%c*)"))
            Deal with a repeat web span2.7;
        if (Regexp::match(&mr, line, L"(%c*){repeat-modules-span}(%c*?){end-span}(%c*)"))
            Deal with a repeat module span2.8;

        if (Regexp::match(&mr, line, L" *{identity-settings} *")) Expand identity-settings2.10;
        if (Regexp::match(&mr, line, L" *{platform-settings} *")) Expand platform-settings2.9;

        if (Regexp::match(&mr, line, L" *{tool} *(%C+) (%C+) (%c+) (%C+) *")) Declare a tool2.11;
        if (Regexp::match(&mr, line, L" *{web} *(%C+) (%C+) (%c+) (%C+) *")) Declare a web2.12;
        if (Regexp::match(&mr, line, L" *{module} *(%C+) (%C+) (%c+) (%C+) *")) Declare a module2.13;

        if (Regexp::match(&mr, line, L"(%c*?) *{dependent-files} *")) Expand dependent-files2.14;
        if (Regexp::match(&mr, line, L"(%c*?) *{dependent-files-for-tool-alone} *(%C+)"))
            Expand dependent-files-for-tool-alone2.16;
        if (Regexp::match(&mr, line, L"(%c*?) *{dependent-files-for-tool-and-modules} *(%C+)"))
            Expand dependent-files-for-tool2.15;
        if (Regexp::match(&mr, line, L"(%c*?) *{dependent-files-for-module} *(%C+)"))
            Expand dependent-files-for-module2.17;
    }
    Regexp::dispose_of(&mr);

    And otherwise copy the line straight through2.18;
}

§2.1. Begin a repeat tool block2.1 =

    int marker = MAKEFILE_TOOL_MOM;
    Begin a repeat block2.1.1;

§2.2. Begin a repeat web block2.2 =

    int marker = MAKEFILE_WEB_MOM;
    Begin a repeat block2.1.1;

§2.3. Begin a repeat module block2.3 =

    int marker = MAKEFILE_MODULE_MOM;
    Begin a repeat block2.1.1;

§2.1.1. Begin a repeat block2.1.1 =

    if (MS->inside_block) Errors::in_text_file("nested repeat blocks are not allowed", tfp);
    MS->inside_block = TRUE;
    MS->repeat_scope = marker;
    MS->repeat_tag = Str::duplicate(mr.exp[0]);
    Str::clear(MS->repeat_block);
    Regexp::dispose_of(&mr);
    return;

§2.4. Deal with a line in a repeat block2.4 =

    WRITE_TO(MS->repeat_block, "%S\n", line);
    return;

§2.5. End a repeat block2.5 =

    if (MS->inside_block == FALSE)
        Errors::in_text_file("{endblock} without {repeatblock}", tfp);
    MS->inside_block = FALSE;
    Makefiles::repeat(OUT, NULL, TRUE, MS->repeat_block, TRUE, NULL, tfp, MS, MS->repeat_scope, MS->repeat_tag);
    Str::clear(MS->repeat_block);
    Regexp::dispose_of(&mr);
    return;

§2.6. Deal with a repeat span2.6 =

    int marker = MAKEFILE_TOOL_MOM;
    Begin a repeat span2.6.1;

§2.7. Deal with a repeat web span2.7 =

    int marker = MAKEFILE_WEB_MOM;
    Begin a repeat span2.6.1;

§2.8. Deal with a repeat module span2.8 =

    int marker = MAKEFILE_MODULE_MOM;
    Begin a repeat span2.6.1;

§2.6.1. Begin a repeat span2.6.1 =

    WRITE("%S", mr.exp[0]);
    Makefiles::repeat(OUT, I" ", FALSE, mr.exp[1], FALSE, NULL, tfp, MS, marker, I"all");
    WRITE("%S\n", mr.exp[2]);
    MS->last_line_was_blank = FALSE;
    Regexp::dispose_of(&mr);
    return;

§2.9. Expand platform-settings2.9 =

    filename *prototype = Filenames::in(path_to_inweb, I"platform-settings.mk");
    MS->allow_commands = FALSE;
    TextFiles::read(prototype, FALSE, "can't open make settings file",
        TRUE, Makefiles::scan_makefile_line, NULL, MS);
    Regexp::dispose_of(&mr);
    MS->allow_commands = TRUE;
    return;

§2.10. Expand identity-settings2.10 =

    WRITE("INWEB = "); Makefiles::pathname_slashed(OUT, path_to_inweb); WRITE("/Tangled/inweb\n");
    pathname *path_to_intest = Pathnames::down(Pathnames::up(path_to_inweb), I"intest");
    WRITE("INTEST = "); Makefiles::pathname_slashed(OUT, path_to_intest); WRITE("/Tangled/intest\n");
    if (MS->for_web) {
        WRITE("MYNAME = %S\n", Pathnames::directory_name(MS->for_web->md->path_to_web));
        WRITE("ME = "); Makefiles::pathname_slashed(OUT, MS->for_web->md->path_to_web);
        WRITE("\n");
        MS->last_line_was_blank = FALSE;
    }
    Regexp::dispose_of(&mr);
    return;

§2.11. Declare a tool2.11 =

    int marker = MAKEFILE_TOOL_MOM;
    dictionary *D = MS->tools_dictionary;
    Declare something2.11.1;

§2.12. Declare a web2.12 =

    int marker = MAKEFILE_WEB_MOM;
    dictionary *D = MS->webs_dictionary;
    Declare something2.11.1;

§2.13. Declare a module2.13 =

    int marker = MAKEFILE_MODULE_MOM;
    dictionary *D = MS->modules_dictionary;
    Declare something2.11.1;

§2.11.1. Declare something2.11.1 =

    WRITE("%SLEAF = %S\n", mr.exp[0], mr.exp[1]);
    WRITE("%SWEB = %S\n", mr.exp[0], mr.exp[2]);
    WRITE("%SMAKER = $(%SWEB)/%S.mk\n", mr.exp[0], mr.exp[0], mr.exp[1]);
    WRITE("%SX = $(%SWEB)/Tangled/%S\n", mr.exp[0], mr.exp[0], mr.exp[1]);
    MS->last_line_was_blank = FALSE;
    web_md *Wm = Reader::load_web_md(Pathnames::from_text(mr.exp[2]), NULL, MS->search_path, TRUE);
    Wm->as_module->module_name = Str::duplicate(mr.exp[0]);
    Wm->as_module->module_tag = Str::duplicate(mr.exp[3]);
    Wm->as_module->origin_marker = marker;
    Dictionaries::create(D, mr.exp[0]);
    Dictionaries::write_value(D, mr.exp[0], Wm);
    Regexp::dispose_of(&mr);
    return;

§2.14. Expand dependent-files2.14 =

    WRITE("%S", mr.exp[0]);
    Makefiles::pattern(OUT, MS->for_web->md->sections_md, MS->for_web->md->contents_filename);
    WRITE("\n");
    MS->last_line_was_blank = FALSE;
    Regexp::dispose_of(&mr);
    return;

§2.15. Expand dependent-files-for-tool2.15 =

    WRITE("%S", mr.exp[0]);
    if (Dictionaries::find(MS->tools_dictionary, mr.exp[1])) {
        web_md *Wm = Dictionaries::read_value(MS->tools_dictionary, mr.exp[1]);
        Makefiles::pattern(OUT, Wm->sections_md, Wm->contents_filename);
    } else if (Dictionaries::find(MS->webs_dictionary, mr.exp[1])) {
        web_md *Wm = Dictionaries::read_value(MS->webs_dictionary, mr.exp[1]);
        Makefiles::pattern(OUT, Wm->sections_md, Wm->contents_filename);
    } else {
        PRINT("Tool %S\n", mr.exp[0]);
        Errors::in_text_file("unknown tool to find dependencies for", tfp);
    }
    WRITE("\n");
    MS->last_line_was_blank = FALSE;
    Regexp::dispose_of(&mr);
    return;

§2.16. Expand dependent-files-for-tool-alone2.16 =

    WRITE("%S", mr.exp[0]);
    if (Dictionaries::find(MS->tools_dictionary, mr.exp[1])) {
        web_md *Wm = Dictionaries::read_value(MS->tools_dictionary, mr.exp[1]);
        Makefiles::pattern(OUT, Wm->as_module->sections_md, Wm->contents_filename);
    } else if (Dictionaries::find(MS->webs_dictionary, mr.exp[1])) {
        web_md *Wm = Dictionaries::read_value(MS->webs_dictionary, mr.exp[1]);
        Makefiles::pattern(OUT, Wm->as_module->sections_md, Wm->contents_filename);
    } else {
        PRINT("Tool %S\n", mr.exp[0]);
        Errors::in_text_file("unknown tool to find dependencies for", tfp);
    }
    WRITE("\n");
    MS->last_line_was_blank = FALSE;
    Regexp::dispose_of(&mr);
    return;

§2.17. Expand dependent-files-for-module2.17 =

    WRITE("%S", mr.exp[0]);
    if (Dictionaries::find(MS->modules_dictionary, mr.exp[1])) {
        web_md *Wm = Dictionaries::read_value(MS->modules_dictionary, mr.exp[1]);
        Makefiles::pattern(OUT, Wm->sections_md, Wm->contents_filename);
    } else {
        Errors::in_text_file("unknown module to find dependencies for", tfp);
        WRITE_TO(STDERR, "-- module name: %S\n", mr.exp[1]);
    }
    WRITE("\n");
    MS->last_line_was_blank = FALSE;
    Regexp::dispose_of(&mr);
    return;

§2.18. And otherwise copy the line straight through2.18 =

    if (Str::len(line) == 0) {
        if (MS->last_line_was_blank == FALSE) WRITE("\n");
        MS->last_line_was_blank = TRUE;
    } else {
        MS->last_line_was_blank = FALSE;
        WRITE("%S\n", line);
    }

§3.

void Makefiles::pathname_slashed(OUTPUT_STREAM, pathname *P) {
    TEMPORARY_TEXT(PT)
    WRITE_TO(PT, "%p", P);
    LOOP_THROUGH_TEXT(pos, PT) {
        wchar_t c = Str::get(pos);
        if (c == ' ') WRITE("\\ ");
        else PUT(c);
    }
    DISCARD_TEXT(PT)
}

void Makefiles::pattern(OUTPUT_STREAM, linked_list *L, filename *F) {
    dictionary *patterns_done = Dictionaries::new(16, TRUE);
    if (F) Add pattern for file F, if not already given3.1;
    section_md *Sm;
    LOOP_OVER_LINKED_LIST(Sm, section_md, L) {
        filename *F = Sm->source_file_for_section;
        Add pattern for file F, if not already given3.1;
    }
}

§3.1. Add pattern for file F, if not already given3.1 =

    pathname *P = Filenames::up(F);
    TEMPORARY_TEXT(leaf_pattern)
    WRITE_TO(leaf_pattern, "%S", Pathnames::directory_name(P));
    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, leaf_pattern, L"Chapter %d*")) {
        Str::clear(leaf_pattern); WRITE_TO(leaf_pattern, "Chapter*");
    } else if (Regexp::match(&mr, leaf_pattern, L"Appendix %C")) {
        Str::clear(leaf_pattern); WRITE_TO(leaf_pattern, "Appendix*");
    }
    Regexp::dispose_of(&mr);
    TEMPORARY_TEXT(tester)
    WRITE_TO(tester, "%p/%S", Pathnames::up(P), leaf_pattern);
    DISCARD_TEXT(leaf_pattern)
    Filenames::write_extension(tester, F);
    if (Dictionaries::find(patterns_done, tester) == NULL) {
        WRITE_TO(Dictionaries::create_text(patterns_done, tester), "got this");
        WRITE(" ");
        LOOP_THROUGH_TEXT(pos, tester) {
            wchar_t c = Str::get(pos);
            if (c == ' ') PUT('\\');
            PUT(c);
        }
    }
    DISCARD_TEXT(tester)

§4. And finally, the following handles repetitions both of blocks and of spans:

void Makefiles::repeat(OUTPUT_STREAM, text_stream *prefix, int every_time, text_stream *matter,
    int as_lines, text_stream *suffix, text_file_position *tfp, makefile_state *MS, int over, text_stream *tag) {
    module *M;
    int c = 0;
    LOOP_OVER(M, module) {
        if ((M->origin_marker == over) &&
            ((Str::eq(tag, I"all")) || (Str::eq(tag, M->module_tag)))) {
            if ((prefix) && ((c++ > 0) || (every_time))) WRITE("%S", prefix);
            if (matter) {
                TEMPORARY_TEXT(line)
                LOOP_THROUGH_TEXT(pos, matter) {
                    if (Str::get(pos) == '\n') {
                        if (as_lines) {
                            Makefiles::scan_makefile_line(line, tfp, (void *) MS);
                            Str::clear(line);
                        }
                    } else {
                        if (Str::get(pos) == '@') {
                            WRITE_TO(line, "%S", M->module_name);
                        } else {
                            PUT_TO(line, Str::get(pos));
                        }
                    }
                }
                if (!as_lines) WRITE("%S", line);
                DISCARD_TEXT(line)
            }
            if (suffix) WRITE("%S", suffix);
        }
    }
}