To compile the variables submodule for a compilation unit, which contains _variable packages.
§1. NVEs. While a nonlocal variable, or NLV, looks like a simple storage location from the perspective of Inform 7 source text — the author assumes there's some memory cell somewhere with this name — it can actually be data expressed in a range of different ways in Inter code. It might indeed be a global variable, but then again it might be an array entry, or even a temporary location on a stack; it might even be a constant.
This range of possible expressions is represented by a nonlocal_variable_emission, or NVE. Each variable in principle has two, a left and a right NVE, though in practice they are often the same. The left NVE tells Inform how to compile assignments to the variable (i.e., when using it as an lvalue); the right NVE how to compile lookups of its value (i.e., as an rvalue).
The NVE structure looks messy, but it's basically a union of four possibilities, of which the last is the default:
typedef struct nonlocal_variable_emission { access as this Inter constant or global variable struct inter_name *iname_form; access as the Inter nothing constant int nothing_form; access as this shared variable on the M-stack struct inter_name *shv_set_ID; int shv_index; int allow_access_even_if_does_not_exist; access as the iname belonging to the NLV itself int use_own_iname; } nonlocal_variable_emission;
- The structure nonlocal_variable_emission is private to this section.
§2. The following is the default:
nonlocal_variable_emission RTVariables::default_nve(void) { nonlocal_variable_emission nve; nve.iname_form = NULL; nve.nothing_form = FALSE; nve.shv_set_ID = NULL; nve.shv_index = -1; nve.allow_access_even_if_does_not_exist = FALSE; nve.use_own_iname = FALSE; return nve; }
§3. So where do these NVEs come from?
nonlocal_variable_emission RTVariables::nve_from_nothing(void) { nonlocal_variable_emission nve = RTVariables::default_nve(); nve.nothing_form = TRUE; return nve; } nonlocal_variable_emission RTVariables::nve_from_iname(inter_name *iname) { nonlocal_variable_emission nve = RTVariables::default_nve(); nve.iname_form = iname; return nve; } nonlocal_variable_emission RTVariables::nve_from_mstack(inter_name *iname, int index, int allow_access_even_if_does_not_exist) { nonlocal_variable_emission nve = RTVariables::default_nve(); nve.shv_set_ID = iname; nve.shv_index = index; nve.allow_access_even_if_does_not_exist = allow_access_even_if_does_not_exist; return nve; } nonlocal_variable_emission RTVariables::nve_from_own_iname(void) { nonlocal_variable_emission nve = RTVariables::default_nve(); nve.use_own_iname = TRUE; return nve; }
§4. As noted above, the left and right NVEs are usually the same:
void RTVariables::set_NVE(nonlocal_variable *nlv, nonlocal_variable_emission nve) { if (nlv == NULL) internal_error("null nlv"); nlv->compilation_data.lvalue_nve = nve; nlv->compilation_data.rvalue_nve = nve; }
§5. This is a particularly useful case, where iname can be the iname of a constant or a global variable in Inter:
void RTVariables::store_in_this_iname(nonlocal_variable *nlv, inter_name *iname) { RTVariables::set_NVE(nlv, RTVariables::nve_from_iname(iname)); } void RTVariables::understood_variable(nonlocal_variable *nlv) { RTVariables::store_in_this_iname(nlv, Hierarchy::find(PARSED_NUMBER_HL)); }
§6. And in particular that's how we handle sentences like "Maximum score translates into Inter as "MAX_SCORE".":
void RTVariables::identifier_translates(nonlocal_variable *nlv, text_stream *name) { if (nlv->compilation_data.nlv_name_translated) { StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_QuantityTranslatedAlready), "this variable has already been translated", "so there must be some duplication somewhere."); } nlv->compilation_data.nlv_name_translated = TRUE; if (Str::eq(name, I"nothing")) { RTVariables::set_NVE(nlv, RTVariables::nve_from_nothing()); } else { inter_name *as_iname = HierarchyLocations::find_by_name(Emit::tree(), name); RTVariables::store_in_this_iname(nlv, as_iname); } } void RTVariables::set_NVE_from_existing(nonlocal_variable *nlv, nonlocal_variable *other) { if (nlv == NULL) internal_error("null nlv"); if (other == NULL) internal_error("null other"); RTVariables::set_NVE(nlv, other->compilation_data.rvalue_nve); }
§7. Left and right NVEs may differ in the case where an NLV is tied to a shared variable which lives fleetingly on the M-stack at runtime. The difference is essentially that a read of a shared variable (the right NVE) will forgive the situation in which that variable does not exist; a write to it (the left NVE) will not. See MStack (in BasicInformKit) for more.
void RTVariables::tie_NLV_to_shared_variable(nonlocal_variable *nlv, shared_variable *shv) { if (nlv == NULL) internal_error("null nlv"); if (SharedVariables::is_actor(shv)) { RTVariables::store_in_this_iname(nlv, Hierarchy::find(ACTOR_HL)); } else { nlv->compilation_data.lvalue_nve = RTVariables::nve_from_mstack(SharedVariables::get_owner_iname(shv), SharedVariables::get_index(shv), FALSE); nlv->compilation_data.rvalue_nve = RTVariables::nve_from_mstack(SharedVariables::get_owner_iname(shv), SharedVariables::get_index(shv), TRUE); } }
§8. And the following code results from a reference to the NVE.
void RTVariables::compile_NVE_as_val(nonlocal_variable *nlv, nonlocal_variable_emission *nve) { if (nve->iname_form) { EmitCode::val_iname(K_value, nve->iname_form); } else if (nve->shv_set_ID) { EmitCode::inv(LOOKUP_BIP); EmitCode::down(); EmitCode::val_iname(K_value, Hierarchy::find(MSTACK_HL)); int ex = MSTVO_HL; if (nve->allow_access_even_if_does_not_exist) ex = MSTVON_HL; EmitCode::call(Hierarchy::find(ex)); EmitCode::down(); EmitCode::val_iname(K_value, nve->shv_set_ID); EmitCode::val_number((inter_ti) nve->shv_index); EmitCode::up(); EmitCode::up(); } else if (nve->use_own_iname) { EmitCode::val_iname(K_value, RTVariables::iname(nlv)); } else if (nve->nothing_form) { EmitCode::val_iname(K_value, Hierarchy::find(NOTHING_HL)); } else { internal_error("improperly formed nve"); } }
§9. Writing without NVEs. NVEs are a very flexible way to describe a storage location, but they do assume that a write can be performed by a STORE_BIP instruction applied to a reference to that location — in other words, by some form of assignment like so:
Something = value; (Somewhere-->20) = value;
And here the term on the left is compiled by wrapping the code produced by RTVariables::compile_NVE_as_val in a REF_IST to make a reference. This is all well and good. But suppose the assignment has to be made by some function instead?
ChangePlayer(value); ... ChangePlayer (val) { ... player = val; ... }
An NVE cannot express the need to compile an assignment entirely differently. So for such cases we provide the ability to set an explicit I6 scheme for writing. In such a schema, *1 means the variable, *2 the value; so, for example, ChangePlayer(*2) could be used in the above example.
void RTVariables::set_write_schema(nonlocal_variable *nlv, text_stream *sch) { nlv->compilation_data.nlv_write_schema = Str::duplicate(sch); } text_stream *RTVariables::get_write_schema(nonlocal_variable *nlv) { if (nlv == NULL) return NULL; return nlv->compilation_data.nlv_write_schema; }
§10. Compilation data. Each nonlocal_variable object contains this data.
typedef struct variable_compilation_data { struct package_request *nlv_package; struct inter_name *nlv_iname; int hierarchy_location_id; int nlv_name_translated; has this been given storage as an I6 variable? struct nonlocal_variable_emission rvalue_nve; struct nonlocal_variable_emission lvalue_nve; struct text_stream *nlv_write_schema; NULL for almost all variables int var_is_initialisable_anyway; meaningful only if not stored in own iname } variable_compilation_data; variable_compilation_data RTVariables::new_compilation_data(void) { variable_compilation_data data; data.nlv_package = NULL; data.hierarchy_location_id = -1; data.nlv_iname = NULL; data.nlv_name_translated = FALSE; data.rvalue_nve = RTVariables::nve_from_own_iname(); data.lvalue_nve = RTVariables::nve_from_own_iname(); data.nlv_write_schema = NULL; data.var_is_initialisable_anyway = FALSE; return data; }
- The structure variable_compilation_data is private to this section.
§11. This function should be used immediately after a variable is created, or
- (preferably) not at all.
void RTVariables::set_hierarchy_location(nonlocal_variable *nlv, int hl) { nlv->compilation_data.hierarchy_location_id = hl; } package_request *RTVariables::package(nonlocal_variable *nlv) { if (nlv->compilation_data.nlv_package == NULL) nlv->compilation_data.nlv_package = Hierarchy::local_package_to(VARIABLES_HAP, nlv->nlv_created_at); return nlv->compilation_data.nlv_package; } inter_name *RTVariables::iname(nonlocal_variable *nlv) { if (nlv->compilation_data.nlv_iname == NULL) { if (nlv->compilation_data.hierarchy_location_id >= 0) nlv->compilation_data.nlv_iname = Hierarchy::find(nlv->compilation_data.hierarchy_location_id); else nlv->compilation_data.nlv_iname = Hierarchy::make_iname_with_memo(VARIABLE_HL, RTVariables::package(nlv), nlv->name); } return nlv->compilation_data.nlv_iname; }
§12. Most variables are stored in the default way, and then they are certainly initialisable. Those stored in some non-standard way are by default not, unless a call to the following has been made:
void RTVariables::make_initialisable(nonlocal_variable *nlv) { nlv->compilation_data.var_is_initialisable_anyway = TRUE; } int RTVariables::stored_in_own_iname(nonlocal_variable *nlv) { if (nlv->compilation_data.lvalue_nve.use_own_iname) return TRUE; return FALSE; } int RTVariables::is_initialisable(nonlocal_variable *nlv) { if (RTVariables::stored_in_own_iname(nlv)) return TRUE; if (nlv->compilation_data.var_is_initialisable_anyway) return TRUE; return FALSE; }
§13. Compilation. For the sake of the index, we recognise variables with "K understood" names and mark them with metadata; this doesn't make them behave any differently in compiled code, of course.
<value-understood-variable-name> ::= <k-kind> understood
- This is Preform grammar, not regular C code.
int RTVariables::compile(inference_subject_family *f, int ignored) { nonlocal_variable *nlv; LOOP_OVER(nlv, nonlocal_variable) { package_request *pack = RTVariables::package(nlv); Hierarchy::apply_metadata_from_wording(pack, VARIABLE_NAME_MD_HL, nlv->name); if ((Wordings::first_wn(nlv->name) >= 0) && (NonlocalVariables::is_global(nlv))) { Hierarchy::apply_metadata_from_number(pack, VARIABLE_AT_MD_HL, (inter_ti) Wordings::first_wn(nlv->name)); heading *h = Headings::of_wording(nlv->name); if (CompletionModule::has_heading_id(h)) Hierarchy::apply_metadata_from_heading(pack, VARIABLE_HEADING_MD_HL, h); Hierarchy::apply_metadata_from_number(pack, VARIABLE_INDEXABLE_MD_HL, ((h) && (h->index_definitions_made_under_this))?1:0); if (<value-understood-variable-name>(nlv->name)) Hierarchy::apply_metadata_from_number(pack, VARIABLE_UNDERSTOOD_MD_HL, 1); if (Wordings::nonempty(nlv->var_documentation_symbol)) Hierarchy::apply_metadata_from_raw_wording(pack, VARIABLE_DOCUMENTATION_MD_HL, Wordings::one_word(Wordings::first_wn(nlv->var_documentation_symbol))); TEMPORARY_TEXT(contents) Kinds::Textual::write(contents, nlv->nlv_kind); Hierarchy::apply_metadata(pack, VARIABLE_CONTENTS_MD_HL, contents); DISCARD_TEXT(contents) } if ((RTVariables::stored_in_own_iname(nlv)) || (nlv->constant_at_run_time == FALSE)) { inter_name *iname = RTVariables::iname(nlv); if (nlv->compilation_data.nlv_name_translated == FALSE) { inter_pair val = RTVariables::initial_value_as_pair(iname, nlv); Emit::variable(iname, nlv->nlv_kind, val); } else { if (nlv->compilation_data.lvalue_nve.iname_form) { inter_symbol *S = InterNames::to_symbol(iname); inter_symbol *H = InterNames::to_symbol(nlv->compilation_data.lvalue_nve.iname_form); Wiring::wire_to(S, H); Hierarchy::apply_metadata_from_iname(pack, VARIABLE_COUNTERPART_MD_HL, nlv->compilation_data.lvalue_nve.iname_form); } } Add any anomalous extras14.1; } if (nlv == max_score_VAR) { inter_name *iname = Hierarchy::make_iname_in(INITIAL_MAX_SCORE_HL, pack); Hierarchy::make_available(iname); if (VariableSubjects::has_initial_value_set(max_score_VAR)) { Emit::initial_value_as_constant(iname, max_score_VAR); } else { Emit::numeric_constant(iname, 0); } } } return TRUE; }
§14.1. Here, an Inter function is compiled which returns the current value of the command prompt variable; see Parser (in CommandParserKit).
Add any anomalous extras14.1 =
if (nlv == NonlocalVariables::command_prompt_variable()) { inter_name *iname = RTVariables::iname(nlv); inter_name *cpt_iname = Hierarchy::make_iname_in(COMMANDPROMPTTEXT_HL, InterNames::location(iname)); packaging_state save = Functions::begin(cpt_iname); EmitCode::inv(RETURN_BIP); EmitCode::down(); EmitCode::val_iname(K_text, iname); EmitCode::up(); Functions::end(save); Hierarchy::make_available(cpt_iname); }
- This code is used in §14.
§15. Initial values. Three functions which all compile the initial value of a variable, in different ways:
void RTVariables::initial_value_as_array_entry(nonlocal_variable *nlv) { value_holster VH = Holsters::new(INTER_DATA_VHMODE); RTVariables::holster_initial_value(&VH, nlv); inter_pair val = Holsters::unholster_to_pair(&VH); EmitArrays::generic_entry(val); } void RTVariables::initial_value_as_val(nonlocal_variable *nlv) { value_holster VH = Holsters::new(INTER_VAL_VHMODE); RTVariables::holster_initial_value(&VH, nlv); Holsters::unholster_to_code_val(Emit::tree(), &VH); } inter_pair RTVariables::initial_value_as_pair(inter_name *iname, nonlocal_variable *nlv) { value_holster VH = Holsters::new(INTER_DATA_VHMODE); packaging_state save = Packaging::enter_home_of(iname); RTVariables::holster_initial_value(&VH, nlv); inter_pair val = Holsters::unholster_to_pair(&VH); Packaging::exit(Emit::tree(), save); return val; }
§16. Which are all powered by the following function.
If the variable has no known initial value, it is given the initial value for its kind where possible: but note that this may not be possible if the source text says something like
Thickness is a kind of value. The carpet nap is a thickness that varies.
without specifying any thicknesses. If that's so, the set of legal thickness values is empty, so the "carpet nap" variable cannot be created in a way which makes its kind safe.
void RTVariables::holster_initial_value(value_holster *VH, nonlocal_variable *nlv) { parse_node *val = NonlocalVariables::substitute_constants( VariableSubjects::get_initial_value( nlv)); if (Node::is(val, UNKNOWN_NT)) { current_sentence = nlv->nlv_created_at; Initialise with the default value of its kind16.1 } else { current_sentence = VariableSubjects::origin_of_initial_value(nlv); if (Lvalues::get_storage_form(val) == NONLOCAL_VARIABLE_NT) Issue a problem for one variable set equal to another16.2 else CompileValues::constant_to_holster(VH, val, nlv->nlv_kind); } }
§16.1. Initialise with the default value of its kind16.1 =
if (DefaultValues::to_holster(VH, nlv->nlv_kind, nlv->name, "variable", FALSE) == FALSE) { if (nlv->var_is_allowed_to_be_zero) { Holsters::holster_pair(VH, InterValuePairs::number(0)); } else { wording W = Kinds::Behaviour::get_name(nlv->nlv_kind, FALSE); Problems::quote_wording(1, nlv->name); Problems::quote_wording(2, W); StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_EmptyDataType)); Problems::issue_problem_segment( "I am unable to put any value into the variable '%1', because " "%2 is a kind of value with no actual values."); Problems::issue_problem_end(); } }
- This code is used in §16.
§16.2. Issue a problem for one variable set equal to another16.2 =
nonlocal_variable *the_other = Node::get_constant_nonlocal_variable(val); if (the_other == NULL) internal_error( "Tried to compile initial value of variable as null variable"); if (the_other == nlv) { Problems::quote_source(1, current_sentence); Problems::quote_wording(2, nlv->name); Problems::quote_kind(3, nlv->nlv_kind); StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_InitialiseQ2)); Problems::issue_problem_segment( "The sentence %1 tells me that '%2', which should be %3 " "that varies, is to have an initial value equal to itself - " "this is such an odd thing to say that I think I must have " "misunderstood."); Problems::issue_problem_end(); } else { Problems::quote_source(1, current_sentence); Problems::quote_wording(2, nlv->name); Problems::quote_kind(3, nlv->nlv_kind); Problems::quote_wording(4, the_other->name); Problems::quote_kind(5, the_other->nlv_kind); StandardProblems::handmade_problem(Task::syntax_tree(), _p_(PM_InitialiseQ1)); Problems::issue_problem_segment( "The sentence %1 tells me that '%2', which should be %3 " "that varies, is to have an initial value equal to '%4', " "which in turn is %5 that varies. At the start of play, " "variable values have to be set equal to definite constants, " "so this is not allowed."); Problems::issue_problem_end(); }
- This code is used in §16.