418 lines
15 KiB
OpenEdge ABL
418 lines
15 KiB
OpenEdge ABL
[ACMESupport::] ACME Support.
|
|
|
|
For generic programming languages by the ACME corporation.
|
|
|
|
@h One Dozen ACME Explosive Tennis Balls.
|
|
Older readers will remember that Wile E. Coyote, when wishing to frustrate
|
|
Road Runner with some ingenious device, would invariably buy it from the Acme
|
|
Corporation, which manufactured everything imaginable. See Wikipedia, "Acme
|
|
Corporation", for much else.
|
|
|
|
For us, ACME is an imaginary programming language, providing generic support
|
|
for comments and syntax colouring. Ironically, this code grew out of a language
|
|
actually called ACME: the 6502 assembler of the same name.
|
|
|
|
=
|
|
void ACMESupport::add_fallbacks(programming_language *pl) {
|
|
if (Methods::provided(pl->methods, PARSE_COMMENT_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, PARSE_COMMENT_TAN_MTID, ACMESupport::parse_comment);
|
|
if (Methods::provided(pl->methods, COMMENT_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, COMMENT_TAN_MTID, ACMESupport::comment);
|
|
if (Methods::provided(pl->methods, SHEBANG_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, SHEBANG_TAN_MTID, ACMESupport::shebang);
|
|
if (Methods::provided(pl->methods, BEFORE_MACRO_EXPANSION_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, BEFORE_MACRO_EXPANSION_TAN_MTID, ACMESupport::before_macro_expansion);
|
|
if (Methods::provided(pl->methods, AFTER_MACRO_EXPANSION_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, AFTER_MACRO_EXPANSION_TAN_MTID, ACMESupport::after_macro_expansion);
|
|
if (Methods::provided(pl->methods, START_DEFN_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, START_DEFN_TAN_MTID, ACMESupport::start_definition);
|
|
if (Methods::provided(pl->methods, PROLONG_DEFN_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, PROLONG_DEFN_TAN_MTID, ACMESupport::prolong_definition);
|
|
if (Methods::provided(pl->methods, END_DEFN_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, END_DEFN_TAN_MTID, ACMESupport::end_definition);
|
|
if (Methods::provided(pl->methods, OPEN_IFDEF_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, OPEN_IFDEF_TAN_MTID, ACMESupport::I6_open_ifdef);
|
|
if (Methods::provided(pl->methods, CLOSE_IFDEF_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, CLOSE_IFDEF_TAN_MTID, ACMESupport::I6_close_ifdef);
|
|
if (Methods::provided(pl->methods, INSERT_LINE_MARKER_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, INSERT_LINE_MARKER_TAN_MTID, ACMESupport::insert_line_marker);
|
|
if (Methods::provided(pl->methods, SUPPRESS_DISCLAIMER_TAN_MTID) == FALSE)
|
|
METHOD_ADD(pl, SUPPRESS_DISCLAIMER_TAN_MTID, ACMESupport::suppress_disclaimer);
|
|
if (Methods::provided(pl->methods, BEGIN_WEAVE_WEA_MTID) == FALSE)
|
|
METHOD_ADD(pl, BEGIN_WEAVE_WEA_MTID, ACMESupport::begin_weave);
|
|
if (Methods::provided(pl->methods, RESET_SYNTAX_COLOURING_WEA_MTID) == FALSE)
|
|
METHOD_ADD(pl, RESET_SYNTAX_COLOURING_WEA_MTID, ACMESupport::reset_syntax_colouring);
|
|
if (Methods::provided(pl->methods, SYNTAX_COLOUR_WEA_MTID) == FALSE)
|
|
METHOD_ADD(pl, SYNTAX_COLOUR_WEA_MTID, ACMESupport::syntax_colour);
|
|
}
|
|
|
|
void ACMESupport::expand(OUTPUT_STREAM, text_stream *prototype, text_stream *S, int N, filename *F) {
|
|
if (Str::len(prototype) > 0) {
|
|
for (int i=0; i<Str::len(prototype); i++) {
|
|
wchar_t c = Str::get_at(prototype, i);
|
|
if ((c == '%') && (Str::get_at(prototype, i+1) == 'S') && (S)) {
|
|
WRITE("%S", S);
|
|
i++;
|
|
} else if ((c == '%') && (Str::get_at(prototype, i+1) == 'd') && (N >= 0)) {
|
|
WRITE("%d", N);
|
|
i++;
|
|
} else if ((c == '%') && (Str::get_at(prototype, i+1) == 'f') && (F)) {
|
|
WRITE("%/f", F);
|
|
i++;
|
|
} else {
|
|
PUT(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@h Tangling methods.
|
|
|
|
=
|
|
void ACMESupport::shebang(programming_language *pl, text_stream *OUT, web *W, tangle_target *target) {
|
|
ACMESupport::expand(OUT, pl->shebang, NULL, -1, NULL);
|
|
}
|
|
|
|
void ACMESupport::before_macro_expansion(programming_language *pl,
|
|
OUTPUT_STREAM, para_macro *pmac) {
|
|
ACMESupport::expand(OUT, pl->before_macro_expansion, NULL, -1, NULL);
|
|
}
|
|
|
|
void ACMESupport::after_macro_expansion(programming_language *pl,
|
|
OUTPUT_STREAM, para_macro *pmac) {
|
|
ACMESupport::expand(OUT, pl->after_macro_expansion, NULL, -1, NULL);
|
|
}
|
|
|
|
int ACMESupport::start_definition(programming_language *pl, text_stream *OUT,
|
|
text_stream *term, text_stream *start, section *S, source_line *L) {
|
|
ACMESupport::expand(OUT, pl->start_definition, term, -1, NULL);
|
|
Tangler::tangle_code(OUT, start, S, L);
|
|
return TRUE;
|
|
}
|
|
|
|
int ACMESupport::prolong_definition(programming_language *pl,
|
|
text_stream *OUT, text_stream *more, section *S, source_line *L) {
|
|
ACMESupport::expand(OUT, pl->prolong_definition, NULL, -1, NULL);
|
|
Tangler::tangle_code(OUT, more, S, L);
|
|
return TRUE;
|
|
}
|
|
|
|
int ACMESupport::end_definition(programming_language *pl,
|
|
text_stream *OUT, section *S, source_line *L) {
|
|
ACMESupport::expand(OUT, pl->end_definition, NULL, -1, NULL);
|
|
return TRUE;
|
|
}
|
|
|
|
void ACMESupport::I6_open_ifdef(programming_language *pl, text_stream *OUT, text_stream *symbol, int sense) {
|
|
if (sense) ACMESupport::expand(OUT, pl->start_ifdef, symbol, -1, NULL);
|
|
else ACMESupport::expand(OUT, pl->start_ifndef, symbol, -1, NULL);
|
|
}
|
|
|
|
void ACMESupport::I6_close_ifdef(programming_language *pl, text_stream *OUT, text_stream *symbol, int sense) {
|
|
if (sense) ACMESupport::expand(OUT, pl->end_ifdef, symbol, -1, NULL);
|
|
else ACMESupport::expand(OUT, pl->end_ifndef, symbol, -1, NULL);
|
|
}
|
|
|
|
void ACMESupport::insert_line_marker(programming_language *pl,
|
|
text_stream *OUT, source_line *L) {
|
|
ACMESupport::expand(OUT, pl->line_marker, NULL,
|
|
L->source.line_count, L->source.text_file_filename);
|
|
}
|
|
|
|
void ACMESupport::comment(programming_language *pl,
|
|
text_stream *OUT, text_stream *comm) {
|
|
if (Str::len(pl->multiline_comment_open) > 0) {
|
|
ACMESupport::expand(OUT, pl->multiline_comment_open, NULL, -1, NULL);
|
|
WRITE(" %S ", comm);
|
|
ACMESupport::expand(OUT, pl->multiline_comment_close, NULL, -1, NULL);
|
|
WRITE("\n");
|
|
}
|
|
else if (Str::len(pl->line_comment) > 0) {
|
|
ACMESupport::expand(OUT, pl->line_comment, NULL, -1, NULL);
|
|
WRITE(" %S\n", comm);
|
|
}
|
|
}
|
|
|
|
int ACMESupport::parse_comment(programming_language *pl,
|
|
text_stream *line, text_stream *part_before_comment, text_stream *part_within_comment) {
|
|
int q_mode = 0, c_mode = FALSE, non_white_space = FALSE, c_position = -1, c_end = -1;
|
|
for (int i=0; i<Str::len(line); i++) {
|
|
wchar_t c = Str::get_at(line, i);
|
|
if (c_mode == 2) {
|
|
if (ACMESupport::text_at(line, i, pl->multiline_comment_close)) {
|
|
c_mode = 0; c_end = i; i += Str::len(pl->multiline_comment_close) - 1;
|
|
}
|
|
} else {
|
|
if ((c_mode == 0) && (!(Characters::is_whitespace(c)))) non_white_space = TRUE;
|
|
if ((c == Str::get_first_char(pl->string_literal_escape)) && (q_mode == 2)) i += 1;
|
|
if ((c == Str::get_first_char(pl->character_literal_escape)) && (q_mode == 1)) i += 1;
|
|
if (c == Str::get_first_char(pl->string_literal)) {
|
|
if (q_mode == 0) q_mode = 2;
|
|
else if (q_mode == 2) q_mode = 0;
|
|
}
|
|
if (c == Str::get_first_char(pl->character_literal)) {
|
|
if (q_mode == 0) q_mode = 1;
|
|
else if (q_mode == 1) q_mode = 0;
|
|
}
|
|
if (ACMESupport::text_at(line, i, pl->multiline_comment_open)) {
|
|
c_mode = 2; c_position = i; non_white_space = FALSE;
|
|
i += Str::len(pl->multiline_comment_open) - 1;
|
|
}
|
|
if (ACMESupport::text_at(line, i, pl->line_comment)) {
|
|
c_mode = 1; c_position = i; c_end = Str::len(line); non_white_space = FALSE;
|
|
i += Str::len(pl->line_comment) - 1;
|
|
}
|
|
}
|
|
}
|
|
if ((c_position >= 0) && (non_white_space == FALSE)) {
|
|
Str::clear(part_before_comment);
|
|
for (int i=0; i<c_position; i++) PUT_TO(part_before_comment, Str::get_at(line, i));
|
|
Str::clear(part_within_comment);
|
|
for (int i=c_position + 2; i<c_end; i++) PUT_TO(part_within_comment, Str::get_at(line, i));
|
|
Str::trim_white_space(part_within_comment);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
int ACMESupport::text_at(text_stream *line, int i, text_stream *pattern) {
|
|
if (Str::len(pattern) == 0) return FALSE;
|
|
if (i < 0) return FALSE;
|
|
if (i + Str::len(pattern) > Str::len(line)) return FALSE;
|
|
LOOP_THROUGH_TEXT(pos, pattern)
|
|
if (Str::get(pos) != Str::get_at(line, i++))
|
|
return FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
@ This is here so that tangling the Standard Rules extension doesn't insert
|
|
a spurious comment betraying Inweb's involvement in the process.
|
|
|
|
=
|
|
int ACMESupport::suppress_disclaimer(programming_language *pl) {
|
|
return pl->suppress_disclaimer;
|
|
}
|
|
|
|
@
|
|
|
|
=
|
|
void ACMESupport::begin_weave(programming_language *pl, section *S, weave_target *wv) {
|
|
reserved_word *rw;
|
|
LOOP_OVER_LINKED_LIST(rw, reserved_word, pl->reserved_words)
|
|
Analyser::mark_reserved_word(S, rw->word, rw->colour);
|
|
}
|
|
|
|
@h Syntax colouring.
|
|
This is a very simple syntax colouring algorithm. The state at any given
|
|
time is a single variable, the current category of code being looked at:
|
|
|
|
=
|
|
void ACMESupport::reset_syntax_colouring(programming_language *pl) {
|
|
colouring_state = PLAIN_COLOUR;
|
|
}
|
|
|
|
@ =
|
|
int ACMESupport::syntax_colour(programming_language *pl, text_stream *OUT, weave_target *wv,
|
|
web *W, chapter *C, section *S, source_line *L, text_stream *matter,
|
|
text_stream *colouring) {
|
|
@<Make preliminary colouring@>;
|
|
@<Spot literal numerical constants@>;
|
|
ACMESupport::execute(S, pl->program, matter, colouring, 0, Str::len(matter));
|
|
return FALSE;
|
|
}
|
|
|
|
@<Make preliminary colouring@> =
|
|
int squote = Str::get_first_char(pl->character_literal);
|
|
int squote_escape = Str::get_first_char(pl->character_literal_escape);
|
|
int dquote = Str::get_first_char(pl->string_literal);
|
|
int dquote_escape = Str::get_first_char(pl->string_literal_escape);
|
|
for (int i=0; i < Str::len(matter); i++) {
|
|
int skip = 0, one_off = -1, will_be = -1;
|
|
switch (colouring_state) {
|
|
case PLAIN_COLOUR: {
|
|
wchar_t c = Str::get_at(matter, i);
|
|
if (c == dquote) {
|
|
colouring_state = STRING_COLOUR;
|
|
break;
|
|
}
|
|
if (c == squote) {
|
|
colouring_state = CHAR_LITERAL_COLOUR;
|
|
break;
|
|
}
|
|
if (ACMESupport::identifier_at(pl, matter, colouring, i))
|
|
one_off = IDENTIFIER_COLOUR;
|
|
break;
|
|
}
|
|
case CHAR_LITERAL_COLOUR: {
|
|
wchar_t c = Str::get_at(matter, i);
|
|
if (c == squote) will_be = PLAIN_COLOUR;
|
|
if (c == squote_escape) skip = 1;
|
|
break;
|
|
}
|
|
case STRING_COLOUR: {
|
|
wchar_t c = Str::get_at(matter, i);
|
|
if (c == dquote) will_be = PLAIN_COLOUR;
|
|
if (c == dquote_escape) skip = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (one_off >= 0) Str::put_at(colouring, i, (char) one_off);
|
|
else Str::put_at(colouring, i, (char) colouring_state);
|
|
if (will_be >= 0) colouring_state = (char) will_be;
|
|
if (skip > 0) i += skip;
|
|
}
|
|
|
|
@<Spot literal numerical constants@> =
|
|
int base = -1, dec_possible = TRUE;
|
|
for (int i=0; i < Str::len(matter); i++) {
|
|
if ((Str::get_at(colouring, i) == PLAIN_COLOUR) ||
|
|
(Str::get_at(colouring, i) == IDENTIFIER_COLOUR)) {
|
|
wchar_t c = Str::get_at(matter, i);
|
|
if (ACMESupport::text_at(matter, i, pl->binary_literal_prefix)) {
|
|
base = 2;
|
|
for (int j=0; j<Str::len(pl->binary_literal_prefix); j++)
|
|
Str::put_at(colouring, i+j, (char) CONSTANT_COLOUR);
|
|
dec_possible = TRUE;
|
|
continue;
|
|
} else if (ACMESupport::text_at(matter, i, pl->octal_literal_prefix)) {
|
|
base = 8;
|
|
for (int j=0; j<Str::len(pl->octal_literal_prefix); j++)
|
|
Str::put_at(colouring, i+j, (char) CONSTANT_COLOUR);
|
|
dec_possible = TRUE;
|
|
continue;
|
|
} else if (ACMESupport::text_at(matter, i, pl->hexadecimal_literal_prefix)) {
|
|
base = 16;
|
|
for (int j=0; j<Str::len(pl->hexadecimal_literal_prefix); j++)
|
|
Str::put_at(colouring, i+j, (char) CONSTANT_COLOUR);
|
|
dec_possible = TRUE;
|
|
continue;
|
|
}
|
|
if ((ACMESupport::text_at(matter, i, pl->negative_literal_prefix)) &&
|
|
(dec_possible) && (base == 0)) {
|
|
base = 10;
|
|
Str::put_at(colouring, i, (char) CONSTANT_COLOUR);
|
|
continue;
|
|
}
|
|
int pass = FALSE;
|
|
switch (base) {
|
|
case -1:
|
|
if ((dec_possible) && (Characters::isdigit(c))) {
|
|
base = 10; pass = TRUE;
|
|
}
|
|
break;
|
|
case 2: if ((c == '0') || (c == '1')) pass = TRUE; break;
|
|
case 10: if (Characters::isdigit(c)) pass = TRUE; break;
|
|
case 16: if (Characters::isdigit(c)) pass = TRUE;
|
|
int d = Characters::tolower(c);
|
|
if ((d == 'a') || (d == 'b') || (d == 'c') ||
|
|
(d == 'd') || (d == 'e') || (d == 'f')) pass = TRUE;
|
|
break;
|
|
}
|
|
if (pass) {
|
|
Str::put_at(colouring, i, (char) CONSTANT_COLOUR);
|
|
} else {
|
|
if (Characters::is_whitespace(c)) dec_possible = TRUE;
|
|
else dec_possible = FALSE;
|
|
base = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
@
|
|
|
|
=
|
|
int ACMESupport::identifier_at(programming_language *pl, text_stream *matter, text_stream *colouring, int i) {
|
|
wchar_t c = Str::get_at(matter, i);
|
|
if ((i > 0) && (Str::get_at(colouring, i-1) == IDENTIFIER_COLOUR)) {
|
|
if ((c == '_') ||
|
|
((c >= 'A') && (c <= 'Z')) ||
|
|
((c >= 'a') && (c <= 'z')) ||
|
|
((c >= '0') && (c <= '9'))) return TRUE;
|
|
if ((c == ':') && (pl->supports_namespaces)) return TRUE;
|
|
} else {
|
|
wchar_t d = 0;
|
|
if (i > 0) d = Str::get_at(matter, i);
|
|
if ((d >= '0') && (d <= '9')) return FALSE;
|
|
if ((c == '_') ||
|
|
((c >= 'A') && (c <= 'Z')) ||
|
|
((c >= 'a') && (c <= 'z'))) return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
@
|
|
|
|
=
|
|
void ACMESupport::execute(section *S, colouring_language_block *block, text_stream *matter,
|
|
text_stream *colouring, int from, int to) {
|
|
if (block == NULL) internal_error("no block");
|
|
colouring_rule *rule;
|
|
LOOP_OVER_LINKED_LIST(rule, colouring_rule, block->rules) {
|
|
if (block->run == CHARACTERS_CRULE_RUN) {
|
|
for (int i=from; i<=to; i++)
|
|
ACMESupport::execute_rule(S, rule, matter, colouring, i, i);
|
|
} else if (block->run == WHOLE_LINE_CRULE_RUN) {
|
|
ACMESupport::execute_rule(S, rule, matter, colouring, from, to);
|
|
} else {
|
|
int ident_from = -1;
|
|
for (int i=from; i<=to; i++) {
|
|
int col = Str::get_at(colouring, i);
|
|
if ((col == block->run) ||
|
|
((block->run == UNQUOTED_COLOUR) &&
|
|
((col != STRING_COLOUR) && (col != CHAR_LITERAL_COLOUR)))) {
|
|
if (ident_from == -1) ident_from = i;
|
|
} else {
|
|
if (ident_from >= 0)
|
|
ACMESupport::execute_rule(S, rule, matter, colouring, ident_from, i-1);
|
|
ident_from = -1;
|
|
}
|
|
}
|
|
if (ident_from >= 0)
|
|
ACMESupport::execute_rule(S, rule, matter, colouring, ident_from, to);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ACMESupport::execute_rule(section *S, colouring_rule *rule, text_stream *matter,
|
|
text_stream *colouring, int from, int to) {
|
|
if (ACMESupport::satisfies(S, rule, matter, colouring, from, to))
|
|
ACMESupport::follow(S, rule, matter, colouring, from, to);
|
|
}
|
|
|
|
int ACMESupport::satisfies(section *S, colouring_rule *rule, text_stream *matter,
|
|
text_stream *colouring, int from, int to) {
|
|
if (Str::len(rule->match_text) > 0) {
|
|
if (rule->match_prefix != NOT_A_RULE_PREFIX) {
|
|
int pos = from;
|
|
if (rule->match_prefix != UNSPACED_RULE_PREFIX) {
|
|
while ((pos > 0) && (Characters::is_whitespace(pos-1))) pos--;
|
|
if ((rule->match_prefix == SPACED_RULE_PREFIX) && (pos == from))
|
|
return FALSE;
|
|
}
|
|
if (ACMESupport::text_at(matter, pos-Str::len(rule->match_text), rule->match_text) == FALSE)
|
|
return FALSE;
|
|
} else {
|
|
if (Str::ne(matter, rule->match_text)) return FALSE;
|
|
}
|
|
} else if (rule->match_keyword_of_colour != NOT_A_COLOUR) {
|
|
TEMPORARY_TEXT(id);
|
|
Str::substr(id, Str::at(matter, from), Str::at(matter, to+1));
|
|
int rw = Analyser::is_reserved_word(S, id, rule->match_keyword_of_colour);
|
|
DISCARD_TEXT(id);
|
|
if (rw == FALSE) return FALSE;
|
|
} else if (rule->match_colour != NOT_A_COLOUR) {
|
|
for (int i=from; i<=to; i++)
|
|
if (Str::get_at(colouring, i) != rule->match_colour)
|
|
return FALSE;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
void ACMESupport::follow(section *S, colouring_rule *rule, text_stream *matter,
|
|
text_stream *colouring, int from, int to) {
|
|
if (rule->execute_block)
|
|
ACMESupport::execute(S, rule->execute_block, matter, colouring, from, to);
|
|
else
|
|
for (int i=from; i<=to; i++)
|
|
Str::put_at(colouring, i, rule->set_to_colour);
|
|
}
|