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"); }
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); }
§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++) { inchar32_t 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; }
§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 bracketed5.1; The comma operator is left-associative and means or5.2; The plus operator is left-associative and means and5.3; The caret operator is unary and means not5.4; A bare symbol name is true if and only if it is declared5.5; The expression is malformed5.6; }
§5.1. Subexpressions can be bracketed5.1 =
match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, cond, U"%((%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 or5.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 malformed5.6;
- This code is used in §5.
§5.3. The plus operator is left-associative and means and5.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 malformed5.6;
- This code is used in §5.
§5.4. The caret operator is unary and means not5.4 =
match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, cond, U"%^(%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 declared5.5 =
match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, cond, U"%i+")) { Regexp::dispose_of(&mr); text_stream *entry = Dictionaries::get_text(defined_symbols, cond); if (Str::eq_wide_string(entry, U"y")) return TRUE; return FALSE; }
- This code is used in §5.
§5.6. The expression is malformed5.6 =
Errors::with_text("malformed condition: %S", cond); return 0;
§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, inchar32_t op) { int bl = 0; for (int k = 0, L = Str::len(cond); k < L; k++) { inchar32_t 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 }