To generate I6 code from intermediate code.
§1. Target. This generator produces Inform 6 source code, using the Vanilla algorithm.
void I6Target::create_generator(void) { code_generator *inform6_generator = Generators::new(I"inform6"); METHOD_ADD(inform6_generator, BEGIN_GENERATION_MTID, I6Target::begin_generation); METHOD_ADD(inform6_generator, MANGLE_IDENTIFIER_MTID, I6Target::mangle); METHOD_ADD(inform6_generator, OFFER_PRAGMA_MTID, I6Target::offer_pragma) METHOD_ADD(inform6_generator, END_GENERATION_MTID, I6Target::end_generation); I6TargetCode::create_generator(inform6_generator); I6TargetObjects::create_generator(inform6_generator); I6TargetConstants::create_generator(inform6_generator); I6TargetVariables::create_generator(inform6_generator); }
§2. We will write a single output file of I6 source code, but segmented as follows:
enum ICL_directives_I7CGS enum compiler_versioning_matter_I7CGS enum attributes_I7CGS enum properties_I7CGS enum global_variables_I7CGS enum global_variables_array_I7CGS enum constants_I7CGS enum fake_actions_I7CGS enum arrays_I7CGS enum classes_I7CGS enum objects_I7CGS enum property_stubs_I7CGS enum functions_I7CGS enum command_grammar_I7CGS
int I6_target_segments[] = { ICL_directives_I7CGS, compiler_versioning_matter_I7CGS, attributes_I7CGS, properties_I7CGS, global_variables_I7CGS, global_variables_array_I7CGS, constants_I7CGS, fake_actions_I7CGS, arrays_I7CGS, classes_I7CGS, objects_I7CGS, property_stubs_I7CGS, functions_I7CGS, command_grammar_I7CGS, -1 };
§3. This generator uses the following state data while it works:
define I6_GEN_DATA(x) ((I6_generation_data *) (gen->generator_private_data))->x
typedef struct I6_generation_data { int attribute_slots_used; int value_ranges_needed; int value_property_holders_needed; int DebugAttribute_seen; int subterfuge_count; int write_orig_source_directives; CLASS_DEFINITION } I6_generation_data; I6_generation_data *I6Target::new_data(void) { I6_generation_data *data = CREATE(I6_generation_data); data->attribute_slots_used = 0; data->value_ranges_needed = FALSE; data->value_property_holders_needed = FALSE; data->DebugAttribute_seen = FALSE; data->subterfuge_count = 0; data->write_orig_source_directives = TRUE; return data; }
- The structure I6_generation_data is accessed in 5/ccn, 5/com and here.
§4. We return FALSE here to signal that we want the Vanilla algorithm to manage the process.
int I6Target::begin_generation(code_generator *gtr, code_generation *gen) { int omit_ur = TRUE; CodeGen::create_segments(gen, I6Target::new_data(), I6_target_segments); Parse the Inform compilation options4.1; Compile some I6 oddities4.2; Compile some veneer replacement code4.3; return FALSE; }
§4.1. Parse the Inform compilation options4.1 =
linked_list *opts = TargetVMs::option_list(gen->for_VM); text_stream *opt; LOOP_OVER_LINKED_LIST(opt, text_stream, opts) { if (Str::eq_insensitive(opt, I"omit-unused-routines")) omit_ur = TRUE; else if (Str::eq_insensitive(opt, I"no-omit-unused-routines")) omit_ur = FALSE; else if (Str::eq_insensitive(opt, I"orig-source-directives")) I6_GEN_DATA(write_orig_source_directives) = TRUE; else if (Str::eq_insensitive(opt, I"no-orig-source-directives")) I6_GEN_DATA(write_orig_source_directives) = FALSE; else { #ifdef PROBLEMS_MODULE Problems::fatal("Unknown compilation format option"); #endif #ifndef PROBLEMS_MODULE Errors::fatal("Unknown compilation format option"); exit(1); #endif } }
- This code is used in §4.
§4.2. Defining a constant called Grammar__Version tells Inform 6 which storage layout to use for command parser grammar. 2 is the shiny, modern one — 1995 not 1993.
The I6 compiler adds a thin layer of hidden code to every program it compiles, called the "veneer". This layer of code requires a global variable called debug_flag to exist, and since that doesn't exist in the Inter tree, we must make it by hand.
The or_tmp_var variable is not significant to I6, and is just a temporary location we will need for the code we are compiling. But this seems a good time to make it.
See the Inform 6 Technical Manual for more on these oddities.
Compile some I6 oddities4.2 =
segmentation_pos saved = CodeGen::select(gen, compiler_versioning_matter_I7CGS); text_stream *OUT = CodeGen::current(gen); WRITE("#ifndef VN_1642;\n"); WRITE("#message fatalerror \"Inform 7 generates code requiring Inform 6.42 or later\";\n"); WRITE("#endif;\n"); WRITE("Constant Grammar__Version 2;\n"); WRITE("Global debug_flag;\n"); WRITE("Global or_tmp_var;\n"); CodeGen::deselect(gen, saved); saved = CodeGen::select(gen, ICL_directives_I7CGS); OUT = CodeGen::current(gen); WRITE("!%% -Cus\n"); WRITE("!%% $ZCODE_LESS_DICT_DATA=1;\n"); WRITE("!%% $LONG_DICT_FLAG_BUG=0;\n"); WRITE("!%% $DICT_IMPLICIT_SINGULAR=1;\n"); WRITE("!%% $DICT_TRUNCATE_FLAG=1;\n"); if (omit_ur) WRITE("!%% $OMIT_UNUSED_ROUTINES=1;\n"); if (TargetVMs::is_16_bit(gen->for_VM) == FALSE) WRITE("!%% $DICT_CHAR_SIZE=4;\n"); CodeGen::deselect(gen, saved);
- This code is used in §4.
§4.3. As noted above, I6 will add a veneer of code to what we compile. That veneer will contain a function called OC__Cl which implements "ofclass", the I6 condition determining whether an object belongs to a given class. The I6 compiler's stock copy of OC__Cl doesn't work right with I7 code, though, so we replace it here with a better one. (The I6 compiler uses our definition in preference to its own.)
We need do this only when compiling to the Z-machine; our replacement function is implemented in pure Z-machine assembly language. See the Z-Machine Standards Document for a specification.
Compile some veneer replacement code4.3 =
segmentation_pos saved = CodeGen::select(gen, functions_I7CGS); text_stream *OUT = CodeGen::current(gen); WRITE("#Ifdef TARGET_ZCODE;\n"); if (I6_GEN_DATA(write_orig_source_directives)) WRITE("#OrigSource \"%s\" %d;\n", __FILE__, __LINE__); WRITE("Global max_z_object;\n"); WRITE("#Ifdef Z__Region;\n"); WRITE("[ OC__Cl obj cla j a n objflag;\n"); INDENT; WRITE("@jl obj 1 ?NotObj;\n"); WRITE("@jg obj max_z_object ?NotObj;\n"); WRITE("@inc objflag;\n"); WRITE("#Ifdef K1_room;\n"); WRITE("@je cla K1_room ?~NotRoom;\n"); WRITE("@test_attr obj mark_as_room ?rtrue;\n"); WRITE("@rfalse;\n"); WRITE(".NotRoom;\n"); WRITE("#Endif;\n"); WRITE("#Ifdef K2_thing;\n"); WRITE("@je cla K2_thing ?~NotObj;\n"); WRITE("@test_attr obj mark_as_thing ?rtrue;\n"); WRITE("@rfalse;\n"); WRITE("#Endif;\n"); WRITE(".NotObj;\n"); WRITE("\n"); WRITE("@je cla Object Class ?ObjOrClass;\n"); WRITE("@je cla Routine String ?RoutOrStr;\n"); WRITE("\n"); WRITE("@jin cla 1 ?~Mistake;\n"); WRITE("\n"); WRITE("@jz objflag ?rfalse;\n"); WRITE("@get_prop_addr obj 2 -> a;\n"); WRITE("@jz a ?rfalse;\n"); WRITE("@get_prop_len a -> n;\n"); WRITE("\n"); WRITE("@div n 2 -> n;\n"); WRITE(".Loop;\n"); WRITE("@loadw a j -> sp;\n"); WRITE("@je sp cla ?rtrue;\n"); WRITE("@inc j;\n"); WRITE("@jl j n ?Loop;\n"); WRITE("@rfalse;\n"); WRITE("\n"); WRITE(".ObjOrClass;\n"); WRITE("@jz objflag ?rfalse;\n"); WRITE("@je cla Object ?JustObj;\n"); WRITE("\n"); WRITE("! So now cla is Class\n"); WRITE("@jg obj String ?~rtrue;\n"); WRITE("@jin obj Class ?rtrue;\n"); WRITE("@rfalse;\n"); WRITE("\n"); WRITE(".JustObj;\n"); WRITE("! So now cla is Object\n"); WRITE("@jg obj String ?~rfalse;\n"); WRITE("@jin obj Class ?rfalse;\n"); WRITE("@rtrue;\n"); WRITE("\n"); WRITE(".RoutOrStr;\n"); WRITE("@jz objflag ?~rfalse;\n"); WRITE("@call_2s Z__Region obj -> sp;\n"); WRITE("@inc sp;\n"); WRITE("@je sp cla ?rtrue;\n"); WRITE("@rfalse;\n"); WRITE("\n"); WRITE(".Mistake;\n"); WRITE("RT__Err(\"apply 'ofclass' for\", cla, -1);\n"); WRITE("rfalse;\n"); OUTDENT; WRITE("];\n"); WRITE("#Endif;\n"); if (I6_GEN_DATA(write_orig_source_directives)) WRITE("#OrigSource;\n"); WRITE("#Endif;\n"); CodeGen::deselect(gen, saved);
- This code is used in §4.
§5. Pragmas are interpreted as ICL directives — ICL being the Inform Configuration Language part of Inform 6, a mini-language for controlling the I6 compiler, able to set command-line switches, memory settings and so on. I6 ordinarily discards lines beginning with exclamation marks as comments, but at the very top of the file, lines beginning !% are read as ICL commands: as soon as any line (including a blank line) doesn't have this signature, I6 exits ICL mode.
void I6Target::offer_pragma(code_generator *gtr, code_generation *gen, inter_tree_node *P, text_stream *tag, text_stream *content) { if (Str::eq(tag, I"Inform6")) { segmentation_pos saved = CodeGen::select(gen, ICL_directives_I7CGS); text_stream *OUT = CodeGen::current(gen); WRITE("!%% %S\n", content); CodeGen::deselect(gen, saved); } }
§6. Names are not mangled: all Inter identifiers are used as-is.
void I6Target::mangle(code_generator *gtr, OUTPUT_STREAM, text_stream *identifier) { WRITE("%S", identifier); }
int I6Target::end_generation(code_generator *gtr, code_generation *gen) { if (gen->defines_random) { segmentation_pos saved = CodeGen::select(gen, compiler_versioning_matter_I7CGS); text_stream *OUT = CodeGen::current(gen); WRITE("Replace random;\n"); CodeGen::deselect(gen, saved); } I6TargetObjects::end_generation(gtr, gen); I6TargetCode::end_generation(gtr, gen); return FALSE; }