Convenient machinery for generating individual Inter instructions.
- §3. Code insertion points
- §8. Levelling
- §12. The current function
- §17. Making material outside of functions
- §24. Making the code inside function bodies
§1. This section provides a necessarily miscellaneous API for generating Inter instructions: there are numerous different instructions, so there are many different functions here, but there are few real ideas.
typedef struct site_production_data { struct code_insertion_point cip_stack[MAX_CIP_STACK_SIZE]; int cip_sp; struct inter_bookmark function_body_start_bookmark; struct inter_bookmark function_locals_bookmark; struct inter_bookmark function_body_code_bookmark; struct inter_package *current_inter_function; } site_production_data;
- The structure site_production_data is private to this section.
§2. The bookmarks here are meaningless unless a function is being compiled, and
then they will be given explicit values, so it really doesn't matter what they
are initialised to. But to avoid any doubt, they will be set to the root of I,
even though they will never be used in that state.
void Produce::clear_site_data(inter_tree *I) { building_site *B = &(I->site); B->sprdata.function_body_start_bookmark = InterBookmark::at_start_of_this_repository(I); B->sprdata.function_locals_bookmark = InterBookmark::at_start_of_this_repository(I); B->sprdata.function_body_code_bookmark = InterBookmark::at_start_of_this_repository(I); B->sprdata.cip_sp = 0; B->sprdata.current_inter_function = NULL; }
§3. Code insertion points. From the caller's point of view, the functions in this section just magically know where in the Inter tree we want to generate code. Such a position is called a code_insertion_point or CIP, and we must maintain it carefully. We keep not just one but a stack of them, so that the caller can interrupt one compilation activity, start another, and then resume the original.
For inform7, this stack in fact never exceeds size 2, i.e., that first
interruption is never interrupted. If we ever need that, we can simply raise
MAX_CIP_STACK_SIZE.
Each CIP contains a further stack of "noted levels" — where certain code blocks, belonging to loop and conditional constructs, are placed. (Every Inter instruction has a level, meaning, its hierarchical depth within the code package: a level 3 instruction is three code blocks deep.) One of the easiest ways to get errors when generating Inter code is to lose track of these levels and generate instructions which are one level off; so, managing levels here makes the caller's life much easier.
define MAX_CIP_STACK_SIZE 2
define MAX_NESTED_NOTEWORTHY_LEVELS 256
typedef struct code_insertion_point { int inter_level; int noted_levels[MAX_NESTED_NOTEWORTHY_LEVELS]; int noted_sp; int level_error_occurred; inter_bookmark *insertion_bm; inter_bookmark saved_bm; } code_insertion_point;
- The structure code_insertion_point is private to this section.
§4. When a new CIP is generated with a new write position, it saves a bookmark holding the previous write position:
void Produce::push_new_code_position_saving(inter_tree *I, inter_bookmark *new_IBM, inter_bookmark old_IBM) { code_insertion_point cip; cip.inter_level = (int) (Produce::baseline(new_IBM) + 2); cip.noted_sp = 2; cip.level_error_occurred = FALSE; cip.insertion_bm = new_IBM; cip.saved_bm = old_IBM; if (I->site.sprdata.cip_sp >= MAX_CIP_STACK_SIZE) internal_error("CIP overflow"); I->site.sprdata.cip_stack[I->site.sprdata.cip_sp++] = cip; }
void Produce::push_new_code_position(inter_tree *I, inter_bookmark *IBM) { Produce::push_new_code_position_saving(I, IBM, InterBookmark::snapshot(Packaging::at(I))); }
void Produce::pop_code_position(inter_tree *I) { if (I->site.sprdata.cip_sp <= 0) internal_error("CIP underflow"); if ((I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1].level_error_occurred) && (problem_count == 0)) internal_error("levelling error in CIP"); Packaging::set_at(I, I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1].saved_bm); I->site.sprdata.cip_sp--; }
inter_bookmark *Produce::at(inter_tree *I) { if (I->site.sprdata.cip_sp <= 0) internal_error("CIP level accessed outside function"); return I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1].insertion_bm; }
§8. Levelling. As noted above, Inter instructions occur at "levels" which need to be kept track of. This returns the level at the current write position:
int Produce::level(inter_tree *I) { if (I->site.sprdata.cip_sp <= 0) internal_error("CIP level accessed outside function"); return I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1].inter_level; }
§9. Produce::down is so called because it takes us one level deeper in the tree; this increases the level by 1. Similarly, Produce::up heads back up towards the root, decreasing the level by 1. The caller should be careful to ensure that calls to these functions exactly match each other: for each down there must be a matching up.
void Produce::down(inter_tree *I) { Produce::set_level(I, Produce::level(I) + 1); } void Produce::up(inter_tree *I) { Produce::set_level(I, Produce::level(I) - 1); }
§10. In addition, the user can call the following to jump the level to some position with respect to the currently code block being written. This should only be used as a last resort when the level has unavoidably been lost track of, e.g., when a schema has to compile a fragment of a code block missing its beginning or end.
The levelling stack is automatically maintained. As new code blocks open (or, more properly, as Inter primitives which take code blocks are generated), the level is automatically pushed. When the current write level washes below these levels, the code blocks in question must be finished, and the levels are popped automatically from the stack.
void Produce::set_level_to_current_code_block_plus(inter_tree *I, int delta) { if (I->site.sprdata.cip_sp <= 0) internal_error("CIP level accessed outside routine"); code_insertion_point *cip = &(I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1]); if (cip->noted_sp <= 0) { cip->level_error_occurred = TRUE; cip->noted_sp = 0; } else { Produce::set_level(I, cip->noted_levels[cip->noted_sp-1] + delta); } }
void Produce::note_level_of_newly_opening_code_block(inter_tree *I) { if (I->site.sprdata.cip_sp <= 0) internal_error("CIP level accessed outside routine"); code_insertion_point *cip = &(I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1]); if (cip->noted_sp >= MAX_NESTED_NOTEWORTHY_LEVELS) return; cip->noted_levels[cip->noted_sp++] = Produce::level(I); } void Produce::set_level(inter_tree *I, int N) { if (I->site.sprdata.cip_sp <= 0) internal_error("CIP level accessed outside function"); code_insertion_point *cip = &(I->site.sprdata.cip_stack[I->site.sprdata.cip_sp-1]); if (N < 2) { cip->level_error_occurred = TRUE; N = 2; } while (cip->noted_sp > 0) { if (cip->noted_levels[cip->noted_sp-1] < N) break; cip->noted_sp--; } cip->inter_level = N; }
§12. The current function. It is also convenient to have this section manage the business of constructing standard function packages.
inter_package *Produce::function_body(inter_tree *I, packaging_state *save, inter_name *iname) { if (Packaging::at(I) == NULL) internal_error("no inter repository"); if (save) { *save = Packaging::enter_home_of(iname); package_request *R = InterNames::location(iname); if ((R == NULL) || (R == LargeScale::main_request(I))) { LOG("Routine outside of package: %n\n", iname); internal_error("routine outside of package"); } } inter_name *block_iname = iname; inter_bookmark save_ib = InterBookmark::snapshot(Packaging::at(I)); Produce::set_function(I, Produce::make_and_set_package(I, block_iname, LargeScale::package_type(I, I"_code"))); Produce::guard(CodeInstruction::new(Packaging::at(I), (int) Produce::baseline(Packaging::at(I)) + 1, NULL)); I->site.sprdata.function_body_start_bookmark = InterBookmark::shifted(Packaging::at(I), IMMEDIATELY_AFTER_NODEPLACEMENT); I->site.sprdata.function_locals_bookmark = InterBookmark::shifted(Packaging::at(I), BEFORE_NODEPLACEMENT); I->site.sprdata.function_body_code_bookmark = InterBookmark::snapshot(Packaging::at(I)); Produce::push_new_code_position_saving(I, &(I->site.sprdata.function_body_code_bookmark), save_ib); return I->site.sprdata.current_inter_function; }
int Produce::function_body_is_open(inter_tree *I) { if (I->site.sprdata.current_inter_function) return TRUE; return FALSE; } inter_bookmark *Produce::function_body_start_bookmark(inter_tree *I) { return &(I->site.sprdata.function_body_start_bookmark); }
§14. The following creates a local symbol, suitable for a local variable or a
label. Note that we return an inter_symbol, not an iname: inames can never
refer to local resources like these.
inter_symbol *Produce::new_local_symbol(inter_tree *I, text_stream *name) { return InterSymbolsTable::create_with_unique_name( InterPackage::scope(I->site.sprdata.current_inter_function), name); }
void Produce::end_function_body(inter_tree *I) { Produce::set_function(I, NULL); Produce::pop_code_position(I); }
§16. If the caller doesn't want to use the mechanism above, and wants to make her
own damn packages, she can call the following: but should be careful to call
twice, first to set to the new function's package, then to reset it to NULL.
void Produce::set_function(inter_tree *I, inter_package *P) { I->site.sprdata.current_inter_function = P; }
§17. Making material outside of functions. That's enough of keeping track of things: from here on, the code in this section
will actually make some Inter. The lower-down APIs in bytecode only return
an inter_error_message if something improper has been done; inter and
inform7, when generating code on their own initiative, must never trigger
Inter errors. So we will guard against them, reacting with an immediate
internal error to halt the compiler if they occur.
void Produce::guard(inter_error_message *ERR) { if ((ERR) && (problem_count == 0)) { InterErrors::issue(ERR); internal_error("inter error"); } }
§18. "Level" is an issue outside of functions, too. In general, material will be generated either inside a package or at the root level. The following returns the level for material being generated in this location — 0 for the root level, or the baseline of the current package plus 1, if we're in a package.
inter_ti Produce::baseline(inter_bookmark *IBM) { if (IBM == NULL) return 0; inter_package *pack = InterBookmark::package(IBM); if (pack == NULL) return 0; if (InterPackage::is_a_root_package(pack)) return 0; if (InterPackage::is_a_function_body(pack)) return (inter_ti) InterPackage::baseline(InterPackage::parent(pack)) + 1; return (inter_ti) InterPackage::baseline(pack) + 1; }
void Produce::nop(inter_tree *I) { Produce::nop_at(Packaging::at(I), 0); } void Produce::nop_at(inter_bookmark *IBM, inter_ti delta) { Produce::guard(NopInstruction::new(IBM, Produce::baseline(IBM) + delta, NULL)); } void Produce::comment(inter_tree *I, text_stream *text) { inter_bookmark *IBM = Packaging::at(I); Produce::guard(CommentInstruction::new(IBM, text, NULL, Produce::baseline(IBM))); }
inter_name *Produce::numeric_constant(inter_tree *I, inter_name *con_iname, kind *K, inter_ti val) { packaging_state save = Packaging::enter_home_of(con_iname); inter_symbol *con_s = InterNames::to_symbol(con_iname); inter_bookmark *IBM = Packaging::at(I); Produce::guard(ConstantInstruction::new(IBM, con_s, Produce::kind_to_type(K), InterValuePairs::number(val), Produce::baseline(IBM), NULL)); Packaging::exit(I, save); return con_iname; }
inter_name *Produce::symbol_constant(inter_tree *I, inter_name *con_iname, kind *K, inter_symbol *val_s) { packaging_state save = Packaging::enter_home_of(con_iname); inter_bookmark *IBM = Packaging::at(I); inter_symbol *con_s = InterNames::to_symbol(con_iname); inter_pair val = InterValuePairs::symbolic(IBM, val_s); Produce::guard(ConstantInstruction::new(IBM, con_s, Produce::kind_to_type(K), val, Produce::baseline(IBM), NULL)); Packaging::exit(I, save); return con_iname; }
§22. Note that this function does two things: creates a new package at the current write position, and then shifts the write position into that new package:
inter_package *Produce::make_and_set_package(inter_tree *I, inter_name *iname, inter_symbol *ptype) { inter_package *P = NULL; TEMPORARY_TEXT(textual_name) WRITE_TO(textual_name, "%n", iname); Produce::guard(PackageInstruction::new(Packaging::at(I), textual_name, InterTypes::unchecked(), TRUE, ptype, Produce::baseline(Packaging::at(I)), NULL, &P)); DISCARD_TEXT(textual_name) if (P) InterBookmark::move_into_package(Packaging::at(I), P); return P; }
§23. We make a new package and return it; but note the +1 here — the package
is created at the level below that in IBM.
inter_package *Produce::make_subpackage(inter_bookmark *IBM, text_stream *name, inter_symbol *ptype) { inter_package *P = NULL; Produce::guard(PackageInstruction::new(IBM, name, InterTypes::unchecked(), TRUE, ptype, (inter_ti) InterBookmark::baseline(IBM) + 1, NULL, &P)); return P; }
§24. Making the code inside function bodies. We begin with invocations: usages of the inv instruction. This of course has
three uses. First, primitives:
void Produce::inv_primitive(inter_tree *I, inter_ti bip) { inter_symbol *prim_symb = Primitives::from_BIP(I, bip); if ((Primitives::takes_code_blocks(bip)) && (bip != CASE_BIP) && (bip != DEFAULT_BIP)) Produce::note_level_of_newly_opening_code_block(I); Produce::guard(InvInstruction::new_primitive(Produce::at(I), prim_symb, (inter_ti) Produce::level(I), NULL)); }
void Produce::rtrue(inter_tree *I) { Produce::inv_primitive(I, RETURN_BIP); Produce::down(I); Produce::val(I, K_value, InterValuePairs::number(1)); /* that is, return "true" */ Produce::up(I); } void Produce::rfalse(inter_tree *I) { Produce::inv_primitive(I, RETURN_BIP); Produce::down(I); Produce::val(I, K_value, InterValuePairs::number(0)); /* that is, return "false" */ Produce::up(I); } void Produce::push(inter_tree *I, inter_name *iname) { Produce::inv_primitive(I, PUSH_BIP); Produce::down(I); Produce::val_iname(I, K_value, iname); Produce::up(I); } void Produce::pull(inter_tree *I, inter_name *iname) { Produce::inv_primitive(I, PULL_BIP); Produce::down(I); Produce::ref_iname(I, K_value, iname); Produce::up(I); }
void Produce::inv_assembly(inter_tree *I, text_stream *opcode) { inter_bookmark *IBM = Produce::at(I); Produce::guard(InvInstruction::new_assembly(IBM, opcode, (inter_ti) Produce::level(I), NULL)); }
void Produce::assembly_marker(inter_tree *I, inter_ti which) { Produce::guard(AssemblyInstruction::new(Produce::at(I), which, (inter_ti) Produce::level(I), NULL)); }
void Produce::inv_call_symbol(inter_tree *I, inter_symbol *fn_s) { Produce::guard(InvInstruction::new_function_call(Produce::at(I), fn_s, (inter_ti) Produce::level(I), NULL)); } void Produce::inv_call_iname(inter_tree *I, inter_name *fn_iname) { Produce::inv_call_symbol(I, InterNames::to_symbol(fn_iname)); } void Produce::inv_indirect_call(inter_tree *I, int arity) { Produce::inv_primitive(I, Primitives::BIP_for_indirect_call_returning_value(arity)); }
void Produce::code(inter_tree *I) { Produce::guard(CodeInstruction::new(Produce::at(I), Produce::level(I), NULL)); } void Produce::evaluation(inter_tree *I) { Produce::guard(EvaluationInstruction::new(Produce::at(I), Produce::level(I), NULL)); } void Produce::reference(inter_tree *I) { Produce::guard(ReferenceInstruction::new(Produce::at(I), Produce::level(I), NULL)); }
void Produce::val_iname(inter_tree *I, kind *K, inter_name *iname) { if (iname == NULL) { if (problem_count == 0) internal_error("no iname"); else Produce::val(I, K_value, InterValuePairs::number(0)); /* for error recovery */ } else { Produce::val_symbol(I, K, InterNames::to_symbol(iname)); } }
void Produce::val_symbol(inter_tree *I, kind *K, inter_symbol *s) { inter_pair val = InterValuePairs::symbolic(Packaging::at(I), s); Produce::val(I, K, val); }
void Produce::val(inter_tree *I, kind *K, inter_pair val) { inter_symbol *val_kind = NULL; if ((K) && (K != K_value)) { val_kind = Produce::kind_to_symbol(K); if (val_kind == NULL) internal_error("no kind for val"); } Produce::guard(ValInstruction::new(Produce::at(I), InterTypes::from_type_name(val_kind), Produce::level(I), val, NULL)); }
void Produce::val_nothing(inter_tree *I) { Produce::val(I, K_value, InterValuePairs::number(0)); } void Produce::val_text(inter_tree *I, text_stream *S) { Produce::val(I, K_value, InterValuePairs::from_text(Packaging::at(I), S)); } void Produce::val_char(inter_tree *I, inchar32_t c) { Produce::val(I, K_value, InterValuePairs::number((inter_ti) c)); } void Produce::val_real(inter_tree *I, double g) { Produce::val(I, K_value, InterValuePairs::real(Packaging::at(I), g)); } void Produce::val_real_from_text(inter_tree *I, text_stream *S) { Produce::val(I, K_value, InterValuePairs::real_from_I6_notation(Packaging::at(I), S)); } void Produce::val_dword(inter_tree *I, text_stream *S) { Produce::val(I, K_value, InterValuePairs::from_singular_dword(Packaging::at(I), S)); }
§34. The ref instruction is simpler. It makes no sense to have a storage reference
to a constant, so we only ever need to refer to inames or their symbols:
void Produce::ref_iname(inter_tree *I, kind *K, inter_name *iname) { Produce::ref_symbol(I, K, InterNames::to_symbol(iname)); } void Produce::ref_symbol(inter_tree *I, kind *K, inter_symbol *s) { inter_pair val = InterValuePairs::symbolic(Packaging::at(I), s); inter_symbol *val_kind = NULL; if ((K) && (K != K_value)) { val_kind = Produce::kind_to_symbol(K); if (val_kind == NULL) internal_error("no kind for ref"); } Produce::guard(RefInstruction::new(Produce::at(I), InterTypes::from_type_name(val_kind), Produce::level(I), val, NULL)); }
§35. cast may yet disappear from Inter: it doesn't really accomplish anything at
present, and is more of a placeholder than anything else.
void Produce::cast(inter_tree *I, kind *F, kind *T) { inter_type F_t = Produce::kind_to_type(F); inter_type T_t = Produce::kind_to_type(T); Produce::guard(CastInstruction::new(Produce::at(I), F_t, T_t, (inter_ti) Produce::level(I), NULL)); } inter_symbol *Produce::kind_to_symbol(kind *K) { #ifdef CORE_MODULE if ((K == NULL) || (K == K_value) || (K->construct == CON_INTERMEDIATE)) return NULL; return InterNames::to_symbol(RTKindDeclarations::iname(K)); #endif #ifndef CORE_MODULE return NULL; #endif } inter_type Produce::kind_to_type(kind *K) { inter_type type = InterTypes::unchecked(); inter_symbol *S = Produce::kind_to_symbol(K); if (S) type = InterTypes::from_type_name(S); return type; } inter_ti Produce::kind_to_TID(inter_bookmark *IBM, kind *K) { inter_type type = Produce::kind_to_type(K); return InterTypes::to_TID_at(IBM, type); }
§36. The following reserves a label, that is, declares that a given name will be that of a label in the function currently being constructed.
Label names must begin with a ., and we enforce that here.
inter_symbol *Produce::reserve_label(inter_tree *I, text_stream *lname) { if (Str::get_first_char(lname) != '.') { TEMPORARY_TEXT(dotted) WRITE_TO(dotted, ".%S", lname); inter_symbol *lab_name = Produce::reserve_label(I, dotted); DISCARD_TEXT(dotted) return lab_name; } inter_symbol *lab_name = Produce::local_exists(I, lname); if (lab_name) return lab_name; lab_name = Produce::new_local_symbol(I, lname); InterSymbol::make_label(lab_name); return lab_name; }
void Produce::place_label(inter_tree *I, inter_symbol *lab_name) { Produce::guard(LabelInstruction::new(Produce::at(I), lab_name, (inter_ti) Produce::level(I), NULL)); }
void Produce::lab(inter_tree *I, inter_symbol *L) { Produce::guard(LabInstruction::new(Produce::at(I), L, (inter_ti) Produce::level(I), NULL)); }
Note that this function is not intended as the way high-level code in inform7 should create a local variable: see Local Variables (in imperative) for that. This function is at a lower level — it does the necessary Inter business, but doesn't add the name tp the current stack frame in inform7.
inter_symbol *Produce::local(inter_tree *I, kind *K, text_stream *lname, text_stream *comm) { if (I->site.sprdata.current_inter_function == NULL) internal_error("local variable emitted outside function"); if (K == NULL) K = K_value; inter_symbol *local_s = Produce::new_local_symbol(I, lname); InterSymbol::make_local(local_s); inter_bookmark *locals_at = &(I->site.sprdata.function_locals_bookmark); if ((comm) && (Str::len(comm) > 0)) Produce::guard(CommentInstruction::new(locals_at, comm, NULL, Produce::baseline(locals_at) + 1)); inter_type type = InterTypes::unchecked(); if ((K) && (K != K_value)) type = InterTypes::from_type_name(Produce::kind_to_symbol(K)); Produce::guard(LocalInstruction::new(locals_at, local_s, type, Produce::baseline(locals_at) + 1, NULL)); return local_s; } inter_symbol *Produce::local_exists(inter_tree *I, text_stream *lname) { return InterSymbolsTable::symbol_from_name( InterPackage::scope(I->site.sprdata.current_inter_function), lname); }
void Produce::provenance(inter_tree *I, text_provenance from) { Produce::guard(ProvenanceInstruction::new_from_provenance(Produce::at(I), from, (inter_ti) Produce::level(I), NULL)); }