Provides conditional rendering of text, depending on context.


§1. About symbols. Documentation is allowed to contain rawtext which varies depending on the context: on what platform it's being written for, or what format it is being rendered to, for example. The context is defined by which symbol names have been defined.

Symbols are C-like identifiers (alphanumeric or underscored). The symbol has been declared if and only if it is an existing key for the following hash, and the associated value is "y".

    dictionary *defined_symbols = NULL;

§2. Starting up. The symbol indoc is always defined, so that, in theory, other programs working on rawtext can distinguish themselves from us (by not defining it). For example,

        {^indoc:}You'll probably never see this paragraph.

provides rawtext visible only if indoc isn't the renderer.

    void Symbols::start_up_symbols(void) {
        Symbols::declare_symbol(I"indoc");
    }

The function Symbols::start_up_symbols is used in 1/mn (§1.1).

§3. Making and unmaking.

    void Symbols::declare_symbol(text_stream *symbol) {
        if (defined_symbols == NULL) defined_symbols = Dictionaries::new(10, TRUE);
        text_stream *entry = Dictionaries::create_text(defined_symbols, symbol);
        Str::copy(entry, I"y");
        LOGIF(SYMBOLS, "Declaring <%S>\n", symbol);
    }

    void Symbols::undeclare_symbol(text_stream *symbol) {
        text_stream *entry = Dictionaries::get_text(defined_symbols, symbol);
        if (entry == NULL) return;
        Str::copy(entry, I"n");
        LOGIF(SYMBOLS, "Undeclaring <%S>\n", symbol);
    }

The function Symbols::declare_symbol is used in §2, 1/ins (§5, §3.2).

The function Symbols::undeclare_symbol is used in 1/ins (§5).

§4. Testing. This returns 1 if the current context matches the condition given, and 0 otherwise.

    int Symbols::perform_ifdef(text_stream *cond) {
        for (int i=0, L=Str::len(cond); i<L; i++) {
            int c = Str::get_at(cond, i);
            if (Characters::is_whitespace(c)) {
                Str::delete_nth_character(cond, i);
                i--; L--;
            }
        }
        int v = Symbols::perform_ifdef_inner(cond);
        LOGIF(SYMBOLS, "Ifdef <%S> --> %s\n", cond, v?"yes":"no");
        return v;
    }

The function Symbols::perform_ifdef is used in §5.1, §5.2, §5.3, §5.4, 2/ss (§6.1), 2/rr (§4.1, §4.6.1).

§5. There is an expression grammar here, which we apply correctly if the condition is well-formed; if it"s a mess, we try to return 0, but don"t go to any trouble to report errors.

Any condition can be bracketed; otherwise we have the unary operator ^ (negation), the binary + (conjunction), and binary , (disjunction), which associate in that order. An atomic condition is true if and only if it is a declared symbol. So for example

^alpha,beta+gamma

is true if either alpha is undeclared, or if both beta and gamma are declared. Whereas

^(alpha,beta)+gamma ^alpha+^beta+gamma

are both true if alpha and beta are undeclared but gamma is declared.

    int Symbols::perform_ifdef_inner(text_stream *cond) {
        <Subexpressions can be bracketed 5.1>;
        <The comma operator is left-associative and means or 5.2>;
        <The plus operator is left-associative and means and 5.3>;
        <The caret operator is unary and means not 5.4>;
        <A bare symbol name is true if and only if it is declared 5.5>;

        <The expression is malformed 5.6>;
    }

The function Symbols::perform_ifdef_inner is used in §4.

§5.1. <Subexpressions can be bracketed 5.1> =

        match_results mr = Regexp::create_mr();
        if (Regexp::match(&mr, cond, L"%((%c*)%)")) {
            int rv = Symbols::perform_ifdef(mr.exp[0]);
            Regexp::dispose_of(&mr);
            return rv;
        }

This code is used in §5.

§5.2. <The comma operator is left-associative and means or 5.2> =

        int k = Symbols::find_operator(cond, ',');
        if (k >= 0) {
            TEMPORARY_TEXT(L);
            TEMPORARY_TEXT(R);
            Str::copy(L, cond);
            Str::truncate(L, k);
            Str::copy_tail(R, cond, k+1);
            int rv = ((Symbols::perform_ifdef(L)) || (Symbols::perform_ifdef(R)));
            DISCARD_TEXT(L);
            DISCARD_TEXT(R);
            return rv;
        } else if (k == -2) <The expression is malformed 5.6>;

This code is used in §5.

§5.3. <The plus operator is left-associative and means and 5.3> =

        int k = Symbols::find_operator(cond, '+');
        if (k >= 0) {
            TEMPORARY_TEXT(L);
            TEMPORARY_TEXT(R);
            Str::copy(L, cond);
            Str::truncate(L, k);
            Str::copy_tail(R, cond, k+1);
            int rv = ((Symbols::perform_ifdef(L)) && (Symbols::perform_ifdef(R)));
            DISCARD_TEXT(L);
            DISCARD_TEXT(R);
            return rv;
        } else if (k == -2) <The expression is malformed 5.6>;

This code is used in §5.

§5.4. <The caret operator is unary and means not 5.4> =

        match_results mr = Regexp::create_mr();
        if (Regexp::match(&mr, cond, L"%^(%c*)")) {
            int rv = Symbols::perform_ifdef(mr.exp[0]);
            Regexp::dispose_of(&mr);
            return rv?FALSE:TRUE;
        }

This code is used in §5.

§5.5. <A bare symbol name is true if and only if it is declared 5.5> =

        match_results mr = Regexp::create_mr();
        if (Regexp::match(&mr, cond, L"%i+")) {
            Regexp::dispose_of(&mr);
            text_stream *entry = Dictionaries::get_text(defined_symbols, cond);
            if (Str::eq_wide_string(entry, L"y")) return TRUE;
            return FALSE;
        }

This code is used in §5.

§5.6. <The expression is malformed 5.6> =

        Errors::with_text("malformed condition: %S", cond); return 0;

This code is used in §5, §5.2, §5.3.

§6. The following looks for a single character op in any unbracketed interior parts of the text cond, and looks out for mismatched brackets on the way.

    int Symbols::find_operator(text_stream *cond, int op) {
        int bl = 0;
        for (int k = 0, L = Str::len(cond); k < L; k++) {
            int ch = Str::get_at(cond, k);
            if (ch == '(') bl++;
            if (ch == ')') bl--;
            if (bl < 0) return -2; Too many close brackets
            if ((bl == 0) && (k > 0) && (k < L - 1) && (ch == op)) {
                return k;
            }
        }
        if (bl != 0) return -2; Too many open brackets
        return -1; Not found
    }

The function Symbols::find_operator is used in §5.2, §5.3.