1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-05 08:34:22 +03:00
inform7/inter/pipeline-module/Chapter 3/Resolve Conditional Compilation Stage.w
2022-05-01 08:47:54 -05:00

255 lines
8.1 KiB
OpenEdge ABL

[ResolveConditionalsStage::] Resolve Conditional Compilation Stage.
To generate the initial state of storage for variables.
@h Pipeline stage.
This stage is intended to run immediately after |load-kit-source|. That will
have produced a sequence of |SPLAT_IST| nodes corresponding to directives,
and some of those will be conditional compilation directives. For example,
we might see a sequence like this:
= (text)
IFDEF_PLM
ROUTINE_PLM
IFNOT_PLM
ARRAY_PLM
ROUTINE_PLM
ENDIF_PLM
=
Clearly this either means a function (the first |ROUTINE_PLM|), or a different
function plus an array. We have to decide that now, because optimisation, code
generation and so on need to know exactly what they are dealing with.
If we allowed kit sources to contain arbitrary conditional compilations, that
would be impossible. But in practice they only need to depend on the constants
which are defined by the VM architecture -- whether 16 or 32 bit; whether
debugging is enabled. And we do know the architecture now. (This is why a kit
has a different binary form for each different architecture supported.) So this
stage collapses the above to either:
= (text)
ROUTINE_PLM
=
or:
= (text)
ARRAY_PLM
ROUTINE_PLM
=
depending on which way the |IFDEF_PLM| comes out. At the end of this stage,
then, none of the directives |IFDEF_PLM|, |IFNDEF_PLM|, |IFTRUE_PLM|,
|IFNOT_PLM| or |ENDIF_PLM| appear anywhere in the tree, and all compilation
is therefore unconditional.
=
void ResolveConditionalsStage::create_pipeline_stage(void) {
ParsingPipelines::new_stage(I"resolve-conditional-compilation",
ResolveConditionalsStage::run, NO_STAGE_ARG, FALSE);
}
int ResolveConditionalsStage::run(pipeline_step *step) {
ResolveConditionalsStage::resolve(step->ephemera.tree);
return TRUE;
}
@h Resolution.
While traversing the tree for conditionals, we need to keep track of the current
state, which we do with the following stack. Each time we pass over the start
of an active conditional, we push its state.
The standard Inform kits never exceed
a nesting depth of about 3, so the following maximum is plenty:
@d MAX_CC_STACK_SIZE 32
=
typedef struct rcc_state {
struct dictionary *I6_level_symbols;
int cc_stack[MAX_CC_STACK_SIZE];
int cc_sp;
} rcc_state;
void ResolveConditionalsStage::resolve(inter_tree *I) {
rcc_state state;
state.I6_level_symbols = Dictionaries::new(1024, TRUE);
state.cc_sp = 0;
InterTree::traverse(I, ResolveConditionalsStage::visitor, &state, NULL, 0);
if (state.cc_sp != 0)
PipelineErrors::kit_error(
"conditional compilation wrongly structured: not enough #endif", NULL);
}
@ Note that when the top of the stack is a block whose body is not to be compiled,
we delete each node we traverse through. (The |InterTree::traverse| function
is written such that this can safely be done.)
=
void ResolveConditionalsStage::visitor(inter_tree *I, inter_tree_node *P, void *v_state) {
rcc_state *state = (rcc_state *) v_state;
int compile_this = TRUE;
for (int i=0; i<state->cc_sp; i++) if (state->cc_stack[i] == FALSE) compile_this = FALSE;
if (Inode::is(P, SPLAT_IST)) {
text_stream *S = SplatInstruction::splatter(P);
switch (SplatInstruction::plm(P)) {
case CONSTANT_PLM:
case GLOBAL_PLM:
case ARRAY_PLM:
case ROUTINE_PLM:
case DEFAULT_PLM:
case STUB_PLM:
@<Symbol definition@>;
break;
case IFDEF_PLM: @<Deal with an IFDEF@>; break;
case IFNDEF_PLM: @<Deal with an IFNDEF@>; break;
case IFTRUE_PLM: @<Deal with an IFTRUE@>; break;
case IFNOT_PLM: @<Deal with an IFNOT@>; break;
case ENDIF_PLM: @<Deal with an ENDIF@>; break;
}
}
if (compile_this == FALSE) NodePlacement::remove(P);
}
@ In order to answer whether or not a symbol is defined... we must look for it.
Note that definitions only count if they are in active code. Here, |Y| is added
to the dictionary when it is reached:
= (text as Inform 6)
Constant X = 1;
#Ifdef X;
Constant Y = 2;
#Endif;
=
But here it is not:
= (text as Inform 6)
Constant X = 1;
#Ifndef X;
Constant Y = 2;
#Endif;
=
@<Symbol definition@> =
if (compile_this) {
TEMPORARY_TEXT(ident)
@<Extract second token into ident@>;
LOGIF(RESOLVING_CONDITIONAL_COMPILATION, "I6 defines %S here\n", ident);
Dictionaries::create(state->I6_level_symbols, ident);
DISCARD_TEXT(ident)
}
@<Deal with an IFDEF@> =
TEMPORARY_TEXT(ident)
@<Extract rest of text into ident@>;
int result = FALSE;
text_stream *symbol_name = ident;
@<Decide whether symbol defined@>;
@<Stack up the result@>;
compile_this = FALSE;
DISCARD_TEXT(ident)
@<Deal with an IFNDEF@> =
TEMPORARY_TEXT(ident)
@<Extract rest of text into ident@>;
int result = FALSE;
text_stream *symbol_name = ident;
@<Decide whether symbol defined@>;
result = (result)?FALSE:TRUE;
@<Stack up the result@>;
compile_this = FALSE;
DISCARD_TEXT(ident)
@<Decide whether symbol defined@> =
inter_symbol *symbol = LargeScale::architectural_symbol(I, symbol_name);
if (symbol) {
result = (InterSymbol::defined_elsewhere(symbol))?FALSE:TRUE;
} else {
if (Dictionaries::find(state->I6_level_symbols, symbol_name)) result = TRUE;
}
LOGIF(RESOLVING_CONDITIONAL_COMPILATION,
"Must decide if %S defined: %s\n", symbol_name, (result)?"yes":"no");
if (Log::aspect_switched_on(RESOLVING_CONDITIONAL_COMPILATION_DA)) LOG_INDENT;
@ The following can test |#Iftrue S == W| only for non-negative integers |W|. It
wouldn't be too hard to test other cases, but we just don't need to. The standard
Inform kits use this only to test |#Iftrue WORDSIZE == 4| or |#Iftrue WORDSIZE == 2|.
@<Deal with an IFTRUE@> =
TEMPORARY_TEXT(ident)
@<Extract rest of text into ident@>;
int result = NOT_APPLICABLE;
text_stream *cond = ident;
match_results mr2 = Regexp::create_mr();
if (Regexp::match(&mr2, cond, L" *(%C+?) *== *(%d+) *")) {
text_stream *identifier = mr2.exp[0];
inter_symbol *symbol =
LargeScale::architectural_symbol(I, identifier);
if (symbol) {
int V = InterSymbol::evaluate_to_int(symbol);
int W = Str::atoi(mr2.exp[1], 0);
if ((V >= 0) && (V == W)) result = TRUE; else result = FALSE;
}
}
if (result == NOT_APPLICABLE) {
PipelineErrors::kit_error(
"conditional compilation is too difficult: #iftrue on %S", cond);
result = FALSE;
}
LOGIF(RESOLVING_CONDITIONAL_COMPILATION, "Must decide if %S: ", cond);
LOGIF(RESOLVING_CONDITIONAL_COMPILATION, "%s\n", (result)?"true":"false");
if (Log::aspect_switched_on(RESOLVING_CONDITIONAL_COMPILATION_DA)) LOG_INDENT;
@<Stack up the result@>;
compile_this = FALSE;
DISCARD_TEXT(ident)
@<Stack up the result@> =
if (state->cc_sp >= MAX_CC_STACK_SIZE) {
state->cc_sp = MAX_CC_STACK_SIZE;
PipelineErrors::kit_error(
"conditional compilation wrongly structured: too many nested #ifdef or #iftrue", NULL);
} else {
state->cc_stack[state->cc_sp++] = result;
}
@<Deal with an IFNOT@> =
LOGIF(RESOLVING_CONDITIONAL_COMPILATION, "ifnot\n");
if (state->cc_sp == 0)
PipelineErrors::kit_error("conditional compilation wrongly structured: #ifnot at top level", NULL);
else
state->cc_stack[state->cc_sp-1] = (state->cc_stack[state->cc_sp-1])?FALSE:TRUE;
compile_this = FALSE;
@<Deal with an ENDIF@> =
if (Log::aspect_switched_on(RESOLVING_CONDITIONAL_COMPILATION_DA)) LOG_OUTDENT;
LOGIF(RESOLVING_CONDITIONAL_COMPILATION, "endif\n");
state->cc_sp--;
if (state->cc_sp < 0) {
state->cc_sp = 0;
PipelineErrors::kit_error("conditional compilation wrongly structured: too many #endif", NULL);
}
compile_this = FALSE;
@ That just leaves some dull code to tokenise the directive. E.g., the second
token of |#Iftrue FROG == 2| is |FROG|; the "rest of text" is |FROG == 2|.
@<Extract second token into ident@> =
int tcount = 0;
LOOP_THROUGH_TEXT(pos, S) {
wchar_t c = Str::get(pos);
if ((c == ' ') || (c == '\t') || (c == '\n')) {
if (tcount == 1) tcount = 2;
else if (tcount == 2) break;
} else {
if (tcount == 0) tcount = 1;
if ((c == ';') || (c == '-')) break;
if (tcount == 2) PUT_TO(ident, c);
}
}
@<Extract rest of text into ident@> =
int tcount = 0;
LOOP_THROUGH_TEXT(pos, S) {
wchar_t c = Str::get(pos);
if ((c == ' ') || (c == '\t') || (c == '\n')) {
if (tcount == 1) tcount = 2;
} else {
if (tcount == 0) tcount = 1;
if ((c == ';') || (c == '-')) break;
if (tcount == 2) PUT_TO(ident, c);
}
}