- Source
- Other Tools
- indoc
- Chapter 1: Setting Up
- Context Symbols
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).
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.
- Back to 'Instructions'
- (This section ends Chapter 1: Setting Up.)