[VanillaObjects::] Vanilla Objects. How the vanilla code generation strategy handles instances, kinds, and properties. @h Properties. Early in code-generation, we declare the properties. Generators might want to represent these in all kinds of ways for the sake of efficiency; on Inform 6, for example, some either-or properties of objects may be represented as "attributes". But that's not our concern. We will try to keep the model as simple as possible here. What we assume is that: (a) A property |P| is represented at runtime by a small word array. (b) The meaning of the first two words, |P-->0| and |P-->1|, is up to the generator. It can put anything it likes in them. (c) |P-->2| is 1 for either-or properties, 0 for all others. (d) |P-->3| is the printed name of the property, for use in debugging or runtime problem messages. (e) |P-->4| onwards is a set of permissions, a concise representation of which instances can have the property in question. This is 0-terminated. @ The biggest complication we face is that the linking process has left us, in some cases, with multiple property declarations for what is actually the same property. For example, this arises when a property is defined in Inform 7 source like so: = (text as Inform 7) A room can be privately-named or publicly-named. The privately-named property translates into Inter as "privately_named". = ...where the property |privately_named| actually originates in a kit, written in Inform 6 notation like so: = (text as Inform 6) Attribute privately_named; = We now have two property declarations, one in the Standard Rules module, the other in the BasicInformKit module. It's tempting to have the linker delete the Standard Rules one and convert references to it to point them to the kit definition, but this is not a good idea because the kit definition doesn't have the metadata or permissions which the Standard Rules definition has. So we keep both in play, and reconcile them in the code below. It gets worse: the Standard Rules properties "lighted" and "lit", though different -- one applies to rooms, one to things -- both translate to the same BasicInformKit property |light|. At present that's the worst case scenario (i.e., three different properties all coinciding) but we won't assume that. So what we do is to work through the properties and group them into equivalence classes by their final identifier names. Here, for example, we recognise these two properties as the same because they both want to be called |privately_named|. By scanning the assimilated properties (i.e. those from kits) first, we ensure that the first one found in each set will be the definitive source of the property. But it will likely be the later members of the set which have the necessary metadata attached. Of course, in the benign case where there is just one Inform 7-level definition of a property, |first_with_name| and |last_with_name| will be the same, and the list will be a singleton. = void VanillaObjects::declare_properties(code_generation *gen) { dictionary *first_with_name = Dictionaries::new(1024, FALSE); /* of |inter_symbol| */ dictionary *last_with_name = Dictionaries::new(1024, FALSE); /* of |inter_symbol| */ dictionary *all_with_name = Dictionaries::new(1024, FALSE); /* of |linked_list| of |inter_symbol| */ inter_symbol *prop_name; LOOP_OVER_LINKED_LIST(prop_name, inter_symbol, gen->assimilated_properties) @; LOOP_OVER_LINKED_LIST(prop_name, inter_symbol, gen->unassimilated_properties) @; LOOP_OVER_LINKED_LIST(prop_name, inter_symbol, gen->assimilated_properties) @; LOOP_OVER_LINKED_LIST(prop_name, inter_symbol, gen->unassimilated_properties) @; } @ = text_stream *name = InterSymbol::trans(prop_name); if (Dictionaries::find(last_with_name, name) == NULL) { text_stream *inner_name = Str::duplicate(name); Dictionaries::create(last_with_name, inner_name); Dictionaries::write_value(last_with_name, inner_name, (void *) prop_name); linked_list *L = NEW_LINKED_LIST(inter_symbol); ADD_TO_LINKED_LIST(prop_name, inter_symbol, L); Dictionaries::create(all_with_name, inner_name); Dictionaries::write_value(all_with_name, inner_name, (void *) L); } else { Dictionaries::write_value(last_with_name, name, (void *) prop_name); linked_list *L = Dictionaries::read_value(all_with_name, name); ADD_TO_LINKED_LIST(prop_name, inter_symbol, L); } @ So here's an annoyance. We will need two identifier names for each property. One is the metadata array, while the other will probably be used by the generator to hold the actual storage -- that other is called the "inner name". In the case of our |privately_named| example, the metadata array will be called something like |A_privately_named|, and any references to the property in kit code or in Inform 7 source text will compile to this array. The inner name will preserve the original identifier |privately_named|, and will likely be used by the final generator for where a property value is actually stored. For Inform 6, for example, we will have: = (text) A_privately_named --> 0 2 --> 1 privately_named (an I6 attribute) --> 2 1 --> 3 "privately named" --> 4 ... permissions follow = In some ways it would be more convenient to use these names the other way around: to call the array itself |privately_named| and have the inner identifier be something like |I_privately_named|. But this fails on Inform 6 in exasperating ways because of the built-in |name| property, whose name cannot be declared or altered. @ = text_stream *name = InterSymbol::trans(prop_name); text_stream *inner_name = NULL; if (Dictionaries::find(first_with_name, name) == NULL) { LOGIF(PROPERTY_ALLOCATION, "! NEW name=%S sname=%S eor=%d assim=%d\n", name, InterSymbol::identifier(prop_name), VanillaObjects::is_either_or_property(prop_name), SymbolAnnotation::get_b(prop_name, ASSIMILATED_IANN)); inner_name = Str::duplicate(name); Dictionaries::create(first_with_name, inner_name); Dictionaries::write_value(first_with_name, inner_name, (void *) prop_name); SymbolAnnotation::set_t(gen->from, InterSymbol::package(prop_name), prop_name, INNER_PROPERTY_NAME_IANN, inner_name); @; } else { LOGIF(PROPERTY_ALLOCATION, "! OLD name=%S sname=%S eor=%d assim=%d\n", name, InterSymbol::identifier(prop_name), VanillaObjects::is_either_or_property(prop_name), SymbolAnnotation::get_b(prop_name, ASSIMILATED_IANN)); inter_symbol *existing_prop_name = (inter_symbol *) Dictionaries::read_value(first_with_name, name); inner_name = VanillaObjects::inner_property_name(gen, existing_prop_name); InterSymbol::set_translate(prop_name, InterSymbol::trans(existing_prop_name)); SymbolAnnotation::set_t(gen->from, InterSymbol::package(prop_name), prop_name, INNER_PROPERTY_NAME_IANN, inner_name); } LOGIF(PROPERTY_ALLOCATION, "! Translation %S, inner name %S\n", InterSymbol::trans(prop_name), VanillaObjects::inner_property_name(gen, prop_name)); @ Note that //Generators::declare_property// calls the generator to ask it to create the first two entries in the metadata array. Those can be anything the generator wants. @ = text_stream *array_name = Str::new(); WRITE_TO(array_name, "A_%S", inner_name); InterSymbol::set_translate(prop_name, array_name); linked_list *all_forms = (linked_list *) Dictionaries::read_value(all_with_name, name); segmentation_pos saved; Generators::begin_array(gen, array_name, prop_name, NULL, WORD_ARRAY_FORMAT, -1, &saved); Generators::declare_property(gen, prop_name, all_forms); @; @; @; Generators::mangled_array_entry(gen, I"NULL", WORD_ARRAY_FORMAT); Generators::end_array(gen, WORD_ARRAY_FORMAT, -1, &saved); @ = if (VanillaObjects::is_either_or_property(prop_name)) Generators::array_entry(gen, I"1", WORD_ARRAY_FORMAT); else Generators::array_entry(gen, I"0", WORD_ARRAY_FORMAT); @ Note that we extract the printed name from the last property in the set, because that will come from an I7 source text definition. @ = inter_symbol *last_prop_name = (inter_symbol *) Dictionaries::read_value(last_with_name, name); text_stream *pname = Metadata::optional_textual(InterSymbol::package(last_prop_name), I"^name"); if (pname == NULL) pname = I""; TEMPORARY_TEXT(entry) CodeGen::select_temporary(gen, entry); Generators::compile_literal_text(gen, pname, TRUE); CodeGen::deselect_temporary(gen); Generators::array_entry(gen, entry, WORD_ARRAY_FORMAT); DISCARD_TEXT(entry) @ Type-safety at runtime is managed with a hybrid of compile-time and runtime checking. Compile-time checking polices all uses of properties of values other than objects, but it will usually allow any object property of any object to be accessed, because it's not usually possible for the typechecker to know if an object value |O| is a vehicle, a direction, and so on. For this reason some runtime checking is needed, and to perform that checking, properties need a list of permissions to be stored in memory. This is where. Note that permissions are accumulated for all of the properties in a given name set. In the case of "lighted" and "lit" and |light|, therefore, the permissions written will be those for "lighted" (rooms, basically) and then those for "lit" (things); |light|, the WorldModelKit original, has no permissions -- assimilated properties never do have. @ = inter_tree *I = gen->from; inter_symbol *eprop_name; LOOP_OVER_LINKED_LIST(eprop_name, inter_symbol, all_forms) { inter_node_list *EVL = InterWarehouse::get_node_list(InterTree::warehouse(I), PropertyInstruction::permissions_list(eprop_name)); @; @; @; } @ = inter_symbol *kind_s; LOOP_OVER_LINKED_LIST(kind_s, inter_symbol, gen->kinds_in_declaration_order) if (VanillaObjects::is_kind_of_object(gen, kind_s)) { inter_tree_node *X; LOOP_THROUGH_INTER_NODE_LIST(X, EVL) { inter_symbol *owner_s = PermissionInstruction::owner(X); if (owner_s == kind_s) Generators::symbol_array_entry(gen, kind_s, WORD_ARRAY_FORMAT); } } @ An unusual feature of Inform as a programming language is that individual objects can be given properties, even when other objects of the same kind may lack them. So: @ = inter_symbol *inst_s; LOOP_OVER_LINKED_LIST(inst_s, inter_symbol, gen->instances_in_declaration_order) if (VanillaObjects::is_kind_of_object(gen, InstanceInstruction::typename(inst_s))) { inter_tree_node *X; LOOP_THROUGH_INTER_NODE_LIST(X, EVL) { inter_symbol *owner_s = PermissionInstruction::owner(X); if (owner_s == inst_s) Generators::symbol_array_entry(gen, inst_s, WORD_ARRAY_FORMAT); } } @ It's happily a rare occurrence, but "object" itself can have properties -- so that every object of any kind has permission to have that. We convey that by giving permission for every top-level kind of object. (There are typically only four of these top-level kinds, and not many properties have these permissions, so it's not as wasteful as it looks.) @ = inter_tree_node *X; LOOP_THROUGH_INTER_NODE_LIST(X, EVL) { inter_symbol *owner_s = PermissionInstruction::owner(X); if (owner_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) { Generators::mangled_array_entry(gen, I"K0_kind", WORD_ARRAY_FORMAT); inter_symbol *kind_s; LOOP_OVER_LINKED_LIST(kind_s, inter_symbol, gen->kinds_in_declaration_order) { if (TypenameInstruction::super(kind_s) == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) { Generators::symbol_array_entry(gen, kind_s, WORD_ARRAY_FORMAT); } } } } @ Generators can then access the inner name (if they want it) thus: = text_stream *VanillaObjects::inner_property_name(code_generation *gen, inter_symbol *prop_name) { text_stream *inner_name = SymbolAnnotation::get_t(prop_name, gen->from, INNER_PROPERTY_NAME_IANN); if (inner_name == NULL) inner_name = I""; return inner_name; } @h Instances, kinds and values of properties. Round two is to make declarations of our kinds and instances. Again, we want to make as few assumptions as possible about the eventual runtime representation of these ideas, but that will not be no assumptions. In particular, whereas generators can stash object properties more or less in any way they like, they are required to stash properties of non-object values in small arrays called "sticks", which mimic table columns. This is imposed by the language itself, which allows properties of values to be defined by tables of values. So while it would be attractive here to make no distinction between objects and other property-owners, we cannot do so. = void VanillaObjects::declare_kinds_and_instances(code_generation *gen) { @; @; @; } @ We start then with kinds which have properties but are not kinds of objects. We want to ensure that no property is assigned more than once (for the same kind), so we use "marks" on those already done. @ = int unique_kovp_id = 0; inter_symbol *kind_s; LOOP_OVER_LINKED_LIST(kind_s, inter_symbol, gen->kinds_in_declaration_order) { if (VanillaObjects::value_kind_with_properties(gen, kind_s)) { segmentation_pos saved; Generators::declare_kind(gen, kind_s, &saved); inter_symbol *prop_name; LOOP_OVER_LINKED_LIST(prop_name, inter_symbol, gen->unassimilated_properties) CodeGen::unmark(prop_name); @; @; Generators::end_kind(gen, kind_s, saved); } } @ = inter_node_list *FL = TypenameInstruction::permissions_list(kind_s); @; @ = inter_symbol *inst_s; LOOP_OVER_LINKED_LIST(inst_s, inter_symbol, gen->instances_in_declaration_order) { if (TypenameInstruction::is_a(InstanceInstruction::typename(inst_s), kind_s)) { inter_node_list *FL = InstanceInstruction::permissions_list(inst_s); @; } } @ = inter_tree_node *X; LOOP_THROUGH_INTER_NODE_LIST(X, FL) { inter_symbol *prop_name = PermissionInstruction::property(X); if (prop_name == NULL) internal_error("no property"); if (CodeGen::marked(prop_name) == FALSE) { CodeGen::mark(prop_name); @; } } @ In the case where a kind of value has been created by table, as in this example: = (text as Inform 7) Planet is a kind of value. The planets are defined by the Table of Outer Planets. Table of Outer Planets planet semimajor axis Jupiter 5 AU Saturn 10 AU Uranus 19 AU Neptune 30 AU Pluto 39 AU = the property "semimajor axis" is already stored in a table column. That becomes our stick array. But in other cases, where the instances have not been created by table, no sticks exist and we must compile them. @ = text_stream *ident = NULL; inter_symbol *store = PermissionInstruction::storage(X); if (store) { ident = InterSymbol::trans(store); } else { ident = Str::new(); WRITE_TO(ident, "KOVP_%d", unique_kovp_id++); @; } Generators::assign_properties(gen, kind_s, prop_name, ident); @ These little arrays are sticks of property values, and they are laid out as if they were column arrays in a Table data structure. This means they must be |TABLE_ARRAY_FORMAT| arrays (which wastes one word of memory) and must have blanked-out table column header words at the front (which wastes a further |COL_HSIZE| words). But the cost is a simple overhead, not rising with the number of instances, and is worth it for simplicity and speed. @ = segmentation_pos saved; Generators::begin_array(gen, ident, NULL, NULL, TABLE_ARRAY_FORMAT, -1, &saved); Generators::array_entry(gen, I"0", TABLE_ARRAY_FORMAT); Generators::array_entry(gen, I"0", TABLE_ARRAY_FORMAT); inter_symbol *inst_s; LOOP_OVER_LINKED_LIST(inst_s, inter_symbol, gen->instances_in_declaration_order) { if (TypenameInstruction::is_a(InstanceInstruction::typename(inst_s), kind_s)) { int found = 0; inter_node_list *PVL = InstanceInstruction::properties_list(inst_s); @; PVL = TypenameInstruction::properties_list(kind_s); @; if (found == 0) Generators::array_entry(gen, I"0", TABLE_ARRAY_FORMAT); } } Generators::end_array(gen, TABLE_ARRAY_FORMAT, -1, &saved); @ = inter_tree_node *Y; LOOP_THROUGH_INTER_NODE_LIST(Y, PVL) { inter_symbol *p_name = PropertyValueInstruction::property(Y); if ((p_name == prop_name) && (found == 0)) { found = 1; inter_pair pair = PropertyValueInstruction::value(Y); TEMPORARY_TEXT(val) CodeGen::select_temporary(gen, val); CodeGen::pair(gen, Y, pair); CodeGen::deselect_temporary(gen); Generators::array_entry(gen, val, TABLE_ARRAY_FORMAT); DISCARD_TEXT(val) } } @ So now for the objects. First we declare each kind of object, first calling //Generators::declare_kind//, then //Generators::assign_property// for each property value, and then //Generators::end_kind//. @ = inter_symbol *kind_s; LOOP_OVER_LINKED_LIST(kind_s, inter_symbol, gen->kinds_in_declaration_order) { if ((kind_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) || (VanillaObjects::is_kind_of_object(gen, kind_s))) { segmentation_pos saved; Generators::declare_kind(gen, kind_s, &saved); VanillaObjects::append(gen, kind_s); inter_node_list *FL = TypenameInstruction::properties_list(kind_s); @; Generators::end_kind(gen, kind_s, saved); } } @ And then the instances. As with kinds, we call //Generators::declare_instance//, then give some property values, then call //Generators::end_instance//. With instances of values, note that we have no property assignment to do: that was all taken care of with the sticks of property values already declared. @ = inter_symbol *inst_s; LOOP_OVER_LINKED_LIST(inst_s, inter_symbol, gen->instances_in_declaration_order) { inter_symbol *inst_kind = InstanceInstruction::typename(inst_s); int N = -1; inter_symbol *object_kind = RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM); if ((object_kind == NULL) || (TypenameInstruction::is_a(inst_kind, object_kind) == FALSE)) N = (int) InterValuePairs::to_number( InstanceInstruction::enumerated_value(inst_s)); segmentation_pos saved; Generators::declare_instance(gen, inst_s, inst_kind, N, &saved); if (TypenameInstruction::is_a(inst_kind, RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM))) { VanillaObjects::append(gen, inst_s); inter_node_list *FL = InstanceInstruction::properties_list(inst_s); @; } Generators::end_instance(gen, inst_s, inst_kind, saved); } @ The following, then, is used either for properties of a kind of object, or properties of an instance of object, and issues a stream of //Generators::assign_property// function calls. @ = inter_tree_node *X; LOOP_THROUGH_INTER_NODE_LIST(X, FL) Generators::assign_property(gen, PropertyValueInstruction::property(X), PropertyValueInstruction::value(X), X); @ That just leaves the following horrible function, which is called for each kind or instance of object, and passes raw splat matter down into the declaration which the generator is making. This is a bad idea, because it presupposes which generator is being used, or at any rate what the syntax will be. It arises from source text like this: = (text as Inform 7) Include (- with before [; Go: return 1; ], -) when defining a rideable vehicle. = ...which should probably not be allowed. The splat is the text between |(-| and |-)| here, and as can be seen, it's in Inform 6 syntax, which would be bad news for, say, the C generator. = void VanillaObjects::append(code_generation *gen, inter_symbol *symb) { text_stream *OUT = CodeGen::current(gen); inter_tree *I = gen->from; text_stream *S = SymbolAnnotation::get_t(symb, I, APPEND_IANN); if (Str::len(S) > 0) Vanilla::splat_matter(OUT, I, S); } @h Utility functions. Returns the weak ID of a kind, which is a small integer known at compile time. = int VanillaObjects::weak_id(inter_symbol *kind_s) { inter_package *pack = InterPackage::container(kind_s->definition); inter_symbol *weak_s = Metadata::optional_symbol(pack, I"^weak_id"); int alt_N = -1; if (weak_s) alt_N = InterSymbol::evaluate_to_int(weak_s); if (alt_N >= 0) return alt_N; return 0; } @ |TRUE| for something like "thing" or "room", but |FALSE| for "object" itself. = int VanillaObjects::is_kind_of_object(code_generation *gen, inter_symbol *kind_s) { inter_symbol *object_kind = RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM); if (object_kind == NULL) return FALSE; if (kind_s == object_kind) return FALSE; if (InterTypes::is_unchecked(InterTypes::from_type_name(kind_s))) return FALSE; if (TypenameInstruction::is_a(kind_s, object_kind)) return TRUE; return FALSE; } @ |TRUE| for a kind which can have properties but is not any sort of object. = int VanillaObjects::value_kind_with_properties(code_generation *gen, inter_symbol *kind_s) { if (VanillaObjects::is_kind_of_object(gen, kind_s)) return FALSE; if (kind_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) return FALSE; if (InterTypes::is_unchecked(InterTypes::from_type_name(kind_s))) return FALSE; inter_node_list *FL = TypenameInstruction::permissions_list(kind_s); if (InterNodeList::empty(FL) == FALSE) return TRUE; inter_symbol *inst_s; LOOP_OVER_LINKED_LIST(inst_s, inter_symbol, gen->instances_in_declaration_order) { if (TypenameInstruction::is_a(InstanceInstruction::typename(inst_s), kind_s)) { inter_node_list *FL = InstanceInstruction::permissions_list(inst_s); if (InterNodeList::empty(FL) == FALSE) return TRUE; } } return FALSE; } @ |TRUE| for a property which might be held by one or more instances which are not objects. = int VanillaObjects::is_property_of_values(code_generation *gen, inter_symbol *prop_s) { inter_tree *I = gen->from; inter_node_list *PL = InterWarehouse::get_node_list( InterTree::warehouse(I), PropertyInstruction::permissions_list(prop_s)); if (PL == NULL) internal_error("no permissions list"); inter_tree_node *X; LOOP_THROUGH_INTER_NODE_LIST(X, PL) { inter_symbol *owner_s = PermissionInstruction::owner(X); if (owner_s == NULL) internal_error("bad owner"); inter_symbol *owner_kind_s = NULL; inter_tree_node *D = InterSymbol::definition(owner_s); if (Inode::is(D, INSTANCE_IST)) { owner_kind_s = InstanceInstruction::typename(owner_s); } else { owner_kind_s = owner_s; } if (VanillaObjects::is_kind_of_object(gen, owner_kind_s) == FALSE) return TRUE; } return FALSE; } @ |TRUE| for an either-or property. = int VanillaObjects::is_either_or_property(inter_symbol *prop_s) { inter_type type = InterTypes::of_symbol(prop_s); if (InterTypes::constructor_code(type) == INT2_ITCONC) return TRUE; return FALSE; } @ The spatial depth is a piece of metadata attached to an instance which gives its depth in the containment tree. This is meaningful only in interactive fiction projects. For example, a map in a crate in an Attic room will have a spatial depth of 2: the crate will have depth 1, the room depth 0. = int VanillaObjects::spatial_depth(inter_symbol *inst_s) { inter_package *pack = InterSymbol::package(inst_s); return (int) Metadata::read_optional_numeric(pack, I"^spatial_depth"); }