Tangler

1. Introduction

This is the source for tangle. This compiles code from a .lit file into runnable code. We do this by going through all the codeblocks, and storing them in an associative array. Then we apply additions and redefinitions so that the array just contains the code for each codeblock (indexed with a string: the codeblock name). Then we find the root codeblocks, i.e. the ones that are a filename, and recursively parse all the codeblocks, following each link and adding in the code for it.

Here is an overview of the file:

{tangler.d 1}
import globals;
import std.string;
import std.stdio;
import parser;
import util;
import std.conv: to;

void tangle(Program p) {
    {tangle function, 3}
}

{writeCode function, 4}

2. Overview

The tangle function will take a program in, go through all the chapters, sections and find all the codeblocks. It will then apply the codeblock with += and :=. Another thing it must do is find the root blocks, that is, the files that need to be generated. Starting with those, it will recursively write code to a file using the writeCode function.

3. The Tangle Function

The tangle function should find the codeblocks, apply the += and :=, find the root codeblocks, and call writeCode from those.

We'll start with these three variables.

{tangle function 3}
Block[string] rootCodeblocks;
Block[string] codeblocks;

getCodeblocks(p, codeblocks, rootCodeblocks);

Added to in section 3

Used in section 1

Now we check if there are any root codeblocks.

{tangle function 3} +=
if (rootCodeblocks.length == 0) {
    warn(p.file, 1, "No file codeblocks, not writing any code");
}

Added to in section 3

Used in section 1

Finally we go through every root codeblock, and run writeCode on it. We open a file (making sure it is in outDir). We get the commentString from the list of commands. Then we call writeCode, which will recursively follow the links and generate all the code.

{tangle function 3} +=
foreach (b; rootCodeblocks) {
    string filename = b.name;
    File f;
    if (!noOutput)
        f = File(outDir ~ "/" ~ filename, "w");

    writeCode(codeblocks, b.name, f, filename, "");
    if (!noOutput)
        f.close();
}

Added to in section 3

Used in section 1

4. The writeCode Function

The writeCode function recursively follows the links inside a codeblock and writes all the code for a codeblock. It also keeps the leading whitespace to make sure indentation in the target file is correct.

{writeCode function 4}
void writeCode(Block[string] codeblocks, string blockName, File file, string filename, string whitespace) {
    Block block = codeblocks[blockName];

    if (block.commentString != "") {
        if (!noOutput)
            file.writeln(whitespace ~ block.commentString.replace("%s", blockName));
    }

    foreach (lineObj; block.lines) {
        string line = lineObj.text;
        string stripLine = strip(line);
        if (stripLine.startsWith("@{") && stripLine.endsWith("}")) {
            string newWS = leadingWS(line);
            auto index = stripLine.length - 1;
            auto newBlockName = stripLine[2..index];
            if (newBlockName == blockName) {
                error(lineObj.file, lineObj.lineNum, "{" ~ blockName ~ "} refers to itself");
                tangleErrors = true;
                return;
            }
            if ((newBlockName in codeblocks) !is null) {
                writeCode(codeblocks, newBlockName, file, filename, whitespace ~ newWS);
            } else {
                error(lineObj.file, lineObj.lineNum, "{" ~ newBlockName ~ "} does not exist");
                tangleErrors = true;
            }
        } else {
            if (!noOutput) {
                if (lineDirectives) {
                    if (lineDirectiveStr != "") {
                        file.writeln(lineDirectiveStr, " ", lineObj.lineNum);
                    } else {
                        file.writeln(block.commentString.replace("%s", to!string(lineObj.lineNum)));
                    }
                }
                file.writeln(whitespace ~ line);
            }
        }
    }
    if (!noOutput)
        file.writeln();
}

Used in section 1