1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-05 08:34:22 +03:00
inform7/inter/final-module/Chapter 5/C Namespace.w
2022-03-13 11:28:33 +00:00

147 lines
5.1 KiB
OpenEdge ABL

[CNamespace::] C Namespace.
How identifiers are used in the C code we generate.
@ =
void CNamespace::initialise(code_generator *gtr) {
METHOD_ADD(gtr, MANGLE_IDENTIFIER_MTID, CNamespace::mangle);
METHOD_ADD(gtr, DECLARE_CONSTANT_MTID, CNamespace::declare_constant);
}
@ A fundamental decision we have to make here is what namespace of identifiers
we will use in the code we generate. If an Inter function is called |example|,
we are going to need to compile that to some C function: what should this be
called?
It seems unwise to call it just |example|, that is, to compile Inter identifiers
directly into C ones. What if the Inter code used the identifier |printf| for
something, for example, or indeed the identifiers |struct| or |unsigned|? Moreover,
unlike the Inform 6 generator, this one is expecting the compile code which will
only be part of a larger program. We want to avoid hogging the entire namespace;
in fact, we want our code to use only names beginning |i7_| or |glulx_|, with
the single exception of |main|, and even that only when |main| is compiled.
@ To that end, we "mangle" identifier names, and this is how. Examples:
= (text)
example i7_mgl_example
##Attack i7_ss_Attack
#g$self i7_ss_gself
=
This is not quite a faithful scheme because, say, |#a#a| and |#aa| both mangle
to the same result, |i7_ss_aa|. But |#| and |$| characters are used extremely
sparingly in Inter, and these can never arise.
=
void CNamespace::mangle(code_generator *gtr, OUTPUT_STREAM, text_stream *identifier) {
if (Str::get_first_char(identifier) == '#') {
WRITE("i7_ss_");
LOOP_THROUGH_TEXT(pos, identifier)
if ((Str::get(pos) != '#') && (Str::get(pos) != '$'))
PUT(Str::get(pos));
} else WRITE("i7_mgl_%S", identifier);
}
void CNamespace::mangle_with(code_generator *gtr, OUTPUT_STREAM, text_stream *identifier,
text_stream *modifier) {
WRITE("i7_%S_%S", modifier, identifier);
}
@ Opcode names are also mangled. Each assembly language opcode will use a
corresponding C function, whose name is mangled from that of the opcode. For
example:
= (text)
@jz i7_opcode_jz
@streamnum i7_opcode_streamnum
=
=
void CNamespace::mangle_opcode(OUTPUT_STREAM, text_stream *opcode) {
WRITE("i7_opcode_");
LOOP_THROUGH_TEXT(pos, opcode)
if (Str::get(pos) != '@')
PUT(Str::get(pos));
}
@ Global variable names are similarly mangled:
= (text)
howmayyou i7_var_howmayyou
=
=
void CNamespace::mangle_variable(OUTPUT_STREAM, text_stream *var) {
WRITE("i7_var_%S", var);
}
@ Local variable names have to be handled slightly differently. This is because
Inter frequently makes use of local variables whose identifiers are also used
for some global construct. Of course, C also allows for this: for example --
= (text as C)
int xerxes = 1;
void govern_Sophene(void) {
int xerxes = 2;
printf("%d\n", xerxes);
}
=
...is legal, and prints 2 when the function is called. So at first sight, there's
no problem giving a local variable the same name as some global construct.
But that does not work if the global definition is made by the C preprocessor
rather than its syntax analyser. Consider:
= (text as C)
#define xerxes 1
void govern_Sophene(void) {
int xerxes = 2;
printf("%d\n", xerxes);
}
=
This throws an error message: |int xerxes = 2;| then reads as |int 1 = 2;|. And
since some of our Inter constructs will indeed result in |#define|d values
rather than named C variables, we cannot allow a local variable name to coincide
with the name of anything else (after mangling).
We avoid this by changing the pre-mangled identifiers for all local variables
to begin with |local_|:
= (text)
original translation mangled translation
"xerxes" "local_xerxes" "i7_mgl_local_xerxes"
=
This is not an elegant trick, but it works nicely enough.
=
void CNamespace::fix_locals(code_generation *gen) {
InterTree::traverse(gen->from, CNamespace::sweep_for_locals, gen, NULL, LOCAL_IST);
}
void CNamespace::sweep_for_locals(inter_tree *I, inter_tree_node *P, void *state) {
inter_symbol *var_name = LocalInstruction::variable(P);
TEMPORARY_TEXT(T)
WRITE_TO(T, "local_%S", InterSymbol::identifier(var_name));
InterSymbol::set_translate(var_name, T);
DISCARD_TEXT(T)
}
@ Constants in Inter are indeed directly converted to |#define|d constants in C,
but with their names of course mangled.
For the reason why |Serial| and |Release| are placed higher-up in the file, see
//C Memory Model//.
=
void CNamespace::declare_constant(code_generator *gtr, code_generation *gen,
inter_symbol *const_s, int form, text_stream *val) {
text_stream *name = InterSymbol::trans(const_s);
int seg = c_constants_I7CGS;
if (Str::eq(name, I"Serial")) seg = c_ids_and_maxima_I7CGS;
if (Str::eq(name, I"Release")) seg = c_ids_and_maxima_I7CGS;
if (Str::eq(name, I"BASICINFORMKIT")) seg = c_ids_and_maxima_I7CGS;
segmentation_pos saved = CodeGen::select_layered(gen, seg,
ConstantInstruction::constant_depth(const_s));
text_stream *OUT = CodeGen::current(gen);
WRITE("#define ");
CNamespace::mangle(gtr, OUT, name);
WRITE(" ");
VanillaConstants::definition_value(gen, form, const_s, val);
WRITE("\n");
CodeGen::deselect(gen, saved);
}