1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-07-07 17:44:22 +03:00
inform7/inter/final-module/Chapter 5/C Object Model.w
Philip Chimento bd0a483b70 Declare i7_properties as extern
I guess this worked as written in Clang, but GCC interprets the
declaration without "extern" as a definition, meaning that the actual
definition later in inform7_clib.c counts as a double definition.
2022-04-14 22:10:00 -07:00

1124 lines
43 KiB
OpenEdge ABL

[CObjectModel::] C Object Model.
How objects, classes and properties are compiled to C.
@h Introduction.
=
void CObjectModel::initialise(code_generator *gtr) {
METHOD_ADD(gtr, PSEUDO_OBJECT_MTID, CObjectModel::pseudo_object);
METHOD_ADD(gtr, DECLARE_INSTANCE_MTID, CObjectModel::declare_instance);
METHOD_ADD(gtr, DECLARE_KIND_MTID, CObjectModel::declare_kind);
METHOD_ADD(gtr, DECLARE_PROPERTY_MTID, CObjectModel::declare_property);
METHOD_ADD(gtr, ASSIGN_PROPERTY_MTID, CObjectModel::assign_property);
METHOD_ADD(gtr, ASSIGN_PROPERTIES_MTID, CObjectModel::assign_properties);
}
@
@d MAX_C_OBJECT_TREE_DEPTH 256
=
typedef struct C_generation_object_model_data {
int owner_id_count;
struct C_property_owner *arrow_chain[MAX_C_OBJECT_TREE_DEPTH];
int property_id_counter;
struct C_property_owner *current_owner;
struct dictionary *declared_properties;
struct linked_list *declared_owners; /* of |C_property_owner| */
struct C_property_owner *compass_instance;
struct C_property_owner *direction_kind;
int value_ranges_needed;
int value_property_holders_needed;
int Class_either_or_properties_not_set;
} C_generation_object_model_data;
void CObjectModel::initialise_data(code_generation *gen) {
C_GEN_DATA(objdata.owner_id_count) = 1;
C_GEN_DATA(objdata.property_id_counter) = 0;
C_GEN_DATA(objdata.declared_properties) = Dictionaries::new(1024, FALSE);
C_GEN_DATA(objdata.declared_owners) = NEW_LINKED_LIST(C_property_owner);
for (int i=0; i<128; i++) C_GEN_DATA(objdata.arrow_chain)[i] = NULL;
C_GEN_DATA(objdata.compass_instance) = NULL;
C_GEN_DATA(objdata.value_ranges_needed) = FALSE;
C_GEN_DATA(objdata.value_property_holders_needed) = FALSE;
C_GEN_DATA(objdata.Class_either_or_properties_not_set) = TRUE;
}
void CObjectModel::begin(code_generation *gen) {
CObjectModel::initialise_data(gen);
CObjectModel::declare_metaclasses(gen);
}
void CObjectModel::end(code_generation *gen) {
CObjectModel::write_i7_initialiser(gen);
CObjectModel::write_i7_initialise_object_tree(gen);
CObjectModel::define_object_value_regions(gen);
CObjectModel::compile_ofclass_array(gen);
CObjectModel::compile_gprop_functions(gen);
segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define i7_max_objects I7VAL_STRINGS_BASE\n");
WRITE("#define i7_no_property_ids %d\n", C_GEN_DATA(objdata.property_id_counter));
CodeGen::deselect(gen, saved);
CObjectModel::make_enumerated_property_arrays(gen);
}
@h The object value-space.
Inter requires that the following values must be distinguishable at runtime:
(a) Instances of object;
(b) Classes, which include kinds of object such as "container", but not other
kinds such as "number";
(c) Constant text values -- note: this does not mean values of the I7 "text"
kind, this means only text literals in Inter;
(d) Functions;
(e) 0, which is also the value of the non-object |nothing|.
Note that there is no requirement for these ranges of value to be contiguous,
or to exhaust the whole range of 32-bit values (and they do not). We provide
a function |i7_metaclass| which returns |Class|, |Object|, |String|, |Routine|
or 0 in cases (a) to (e), or 0 for any values not fitting any of these: this
function implements the |!metaclass| primitive.
@ In this C runtime, |nothing| will be 0, as is mandatory; |Class|, |Object|,
|String| and |Routine| will be 1 to 4 respectively; values from 5 upwards will
be assigned to objects and classes as they arise -- note that these mix freely;
string values will occupy a contiguous range |I7VAL_STRINGS_BASE| to
|I7VAL_FUNCTIONS_BASE-1|; and function values will be in tha range
|I7VAL_FUNCTIONS_BASE| to |0x7FFFFFFF|, though they will certainly not fill it.
=
void CObjectModel::define_object_value_regions(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("i7word_t i7_metaclass_of[] = {\n"); INDENT;
WRITE("0\n");
C_property_owner *co;
LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_owners)) {
WRITE(", ");
if (co->is_class) Generators::mangle(gen, OUT, I"Class");
else Generators::mangle(gen, OUT, I"Object");
WRITE("\n");
}
OUTDENT; WRITE(" };\n");
int b = C_GEN_DATA(objdata.owner_id_count);
WRITE("#define I7VAL_STRINGS_BASE %d\n", b);
WRITE("#define I7VAL_FUNCTIONS_BASE %d\n", b + CLiteralsModel::size_of_String_area(gen));
CodeGen::deselect(gen, saved);
}
@ Those decisions give us the following |i7_metaclass| function:
= (text to inform7_clib.h)
i7word_t i7_metaclass(i7process_t *proc, i7word_t id);
=
= (text to inform7_clib.c)
i7word_t i7_metaclass(i7process_t *proc, i7word_t id) {
if (id <= 0) return 0;
if (id >= I7VAL_FUNCTIONS_BASE) return i7_mgl_Routine;
if (id >= I7VAL_STRINGS_BASE) return i7_mgl_String;
return i7_metaclass_of[id];
}
=
@h Property owners.
We use the term "property owner" to mean either a kind of object, or an instance
of object. This is a little loose since instances of enumerated non-object kinds
can also have properties, but those, as we'll later see, are stored quite differently.
Each property owner has a unique ID number. The special ID 0 is reserved for |nothing|,
meaning the absence of such an owner, so we can only use 1 upwards; and as we've seen,
1 to 4 are used for the four metaclasses |Class|, |Object|, |String| and |Routine|.
After that, it's first come, first served.
Each owner has a "class", which is always the name of another owner: except that
the owner |Class| has the class name |Class|, i.e., itself.
Instances, though not of course classes, will also end up as part of an object
containment tree; so we record the initial state of that three here. For classes,
of course, |initial_parent|, |initial_sibling| and |initial_child| will remain |NULL|.
=
typedef struct C_property_owner {
int id;
int is_class;
struct text_stream *name;
struct text_stream *class;
struct linked_list *property_values; /* of |C_pv_pair| */
struct C_property_owner *initial_parent;
struct C_property_owner *initial_sibling;
struct C_property_owner *initial_child;
CLASS_DEFINITION
} C_property_owner;
C_property_owner *CObjectModel::new_owner(code_generation *gen, int id, text_stream *name,
text_stream *class_name, int is_class) {
if (Str::len(name) == 0) internal_error("nameless property owner");
C_property_owner *co = CREATE(C_property_owner);
co->id = id;
co->name = Str::duplicate(name);
co->class = Str::duplicate(class_name);
co->is_class = is_class;
co->property_values = NEW_LINKED_LIST(C_pv_pair);
co->initial_parent = NULL;
co->initial_sibling = NULL;
co->initial_child = NULL;
C_GEN_DATA(objdata.current_owner) = co;
ADD_TO_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_owners));
return co;
}
@ The (constant) array |i7_class_of[id]| accepts any ID for a class or instance,
and evaluates to the ID of its classname. So, for example, |i7_class_of[1] == 1|
expresses that the classname of |Class| is |Class| itself. Here we compile
a declaration for that array.
=
void CObjectModel::compile_ofclass_array(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("i7word_t i7_class_of[] = { 0");
C_property_owner *co;
LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_owners)) {
WRITE(", "); Generators::mangle(gen, OUT, co->class);
}
WRITE(" };\n");
CodeGen::deselect(gen, saved);
}
@ The existence of the |i7_class_of| array at runtime makes it possible to
implement the primitive |!ofclass| reasonably efficiently. Note that it may need
to recurse up the class hierarchy. If A is of class B whose superclass is C, then
|i7_ofclass(A, B)| and |i7_ofclass(A, C)| are both true, as it |i7_ofclass(B, C)|.
= (text to inform7_clib.h)
int i7_ofclass(i7process_t *proc, i7word_t id, i7word_t cl_id);
=
= (text to inform7_clib.c)
int i7_ofclass(i7process_t *proc, i7word_t id, i7word_t cl_id) {
if ((id <= 0) || (cl_id <= 0)) return 0;
if (id >= I7VAL_FUNCTIONS_BASE) {
if (cl_id == i7_mgl_Routine) return 1;
return 0;
}
if (id >= I7VAL_STRINGS_BASE) {
if (cl_id == i7_mgl_String) return 1;
return 0;
}
if (id == i7_mgl_Class) {
if (cl_id == i7_mgl_Class) return 1;
return 0;
}
if (cl_id == i7_mgl_Object) {
if (i7_metaclass_of[id] == i7_mgl_Object) return 1;
return 0;
}
int cl_found = i7_class_of[id];
while (cl_found != i7_mgl_Class) {
if (cl_id == cl_found) return 1;
cl_found = i7_class_of[cl_found];
}
return 0;
}
=
@ Here we compile code to initialise the tree. This happens in two stages: first
the tree is blanked out so that nothing contains anything else, and that's done
with an unchanging function in the C library:
= (text to inform7_clib.h)
void i7_empty_object_tree(i7process_t *proc);
=
= (text to inform7_clib.c)
void i7_empty_object_tree(i7process_t *proc) {
proc->state.object_tree_parent = i7_calloc(proc, i7_max_objects, sizeof(i7word_t));
proc->state.object_tree_child = i7_calloc(proc, i7_max_objects, sizeof(i7word_t));
proc->state.object_tree_sibling = i7_calloc(proc, i7_max_objects, sizeof(i7word_t));
for (int i=0; i<i7_max_objects; i++) {
proc->state.object_tree_parent[i] = 0;
proc->state.object_tree_child[i] = 0;
proc->state.object_tree_sibling[i] = 0;
}
}
=
@ And secondly, there is dynamic code (i.e. different for different compilations)
to store the initial values as recorded in the |initial_*| fields:
=
void CObjectModel::write_i7_initialise_object_tree(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_initialiser_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("void i7_initialise_object_tree(i7process_t *proc) {\n"); INDENT;
C_property_owner *co;
LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_owners)) {
if (co->initial_parent) {
WRITE("proc->state.object_tree_parent[");
Generators::mangle(gen, OUT, co->name);
WRITE("] = ");
Generators::mangle(gen, OUT, co->initial_parent->name);
WRITE(";\n");
}
if (co->initial_sibling) {
WRITE("proc->state.object_tree_sibling[");
Generators::mangle(gen, OUT, co->name);
WRITE("] = ");
Generators::mangle(gen, OUT, co->initial_sibling->name);
WRITE(";\n");
}
if (co->initial_child) {
WRITE("proc->state.object_tree_child[");
Generators::mangle(gen, OUT, co->name);
WRITE("] = ");
Generators::mangle(gen, OUT, co->initial_child->name);
WRITE(";\n");
}
}
OUTDENT; WRITE("}\n");
CodeGen::deselect(gen, saved);
}
@h Runtime classes.
Classes arise either (i) as one of the four fundamental metaclasses, which
we automatically declare at the start of each run, or (ii) when a kind of
object is declared. Here is (i):
=
void CObjectModel::declare_metaclasses(code_generation *gen) {
CObjectModel::new_runtime_class(gen, I"Class", NULL, I"Class");
CObjectModel::new_runtime_class(gen, I"Object", NULL, I"Class");
CObjectModel::new_runtime_class(gen, I"String", NULL, I"Class");
CObjectModel::new_runtime_class(gen, I"Routine", NULL, I"Class");
}
@ And here is (ii):
=
void CObjectModel::declare_kind(code_generator *gtr, code_generation *gen,
inter_symbol *kind_s, segmentation_pos *saved) {
if ((kind_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) ||
(VanillaObjects::is_kind_of_object(gen, kind_s)))
@<Declare a kind of object@>
else if (VanillaObjects::value_kind_with_properties(gen, kind_s))
CObjectModel::vph_object(gen, kind_s);
}
@<Declare a kind of object@> =
text_stream *class_name = InterSymbol::trans(kind_s);
text_stream *printed_name = Metadata::optional_textual(
InterPackage::container(kind_s->definition), I"^printed_name");
text_stream *super_class = NULL;
inter_symbol *super_name = TypenameInstruction::super(kind_s);
if (super_name) super_class = InterSymbol::trans(super_name);
if (Str::len(super_class) == 0) super_class = I"Class";
CObjectModel::new_runtime_class(gen, class_name, printed_name, super_class);
@ In either case (i) or (ii) the following is called:
=
void CObjectModel::new_runtime_class(code_generation *gen, text_stream *class_name,
text_stream *printed_name, text_stream *super_class) {
int id = C_GEN_DATA(objdata.owner_id_count)++;
segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define "); Generators::mangle(gen, OUT, class_name); WRITE(" %d\n", id);
CodeGen::deselect(gen, saved);
if (printed_name) {
segmentation_pos saved = CodeGen::select(gen, c_kinds_symbols_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define %S %d\n", CTarget::symbols_header_identifier(gen, I"K", printed_name), id);
CodeGen::deselect(gen, saved);
}
CObjectModel::new_owner(gen, id, class_name, super_class, TRUE);
}
@h Runtime instances.
These arise either (i) as pseudo-objects provided by kits -- the Inform 7
compiler never itself generates pseudo-objects; or (ii) as property-holder
objects to hold the properties of an enumerated non-object kind, where one
such object exists for each such kind; or (iii), the most obvious way, as
the runtime form of instances of Inform 7 objects. For example, the rooms,
things and people of a work of interactive fiction would each be cases of (iii).
Here is (i). After a typical IF run through Inform 7, this produces only
two pseudo-objects, |Compass| and |thedark|.
=
void CObjectModel::pseudo_object(code_generator *gtr, code_generation *gen, text_stream *obj_name) {
C_property_owner *obj = CObjectModel::new_runtime_object(gtr, gen, I"Object", obj_name, -1, FALSE);
if (Str::eq(obj_name, I"Compass")) C_GEN_DATA(objdata.compass_instance) = obj;
}
@ Here is (ii). Each enumerated kind produces one of these. In a typical
IF run, for example, there is one for the kind "scene".
=
void CObjectModel::vph_object(code_generation *gen, inter_symbol *kind_s) {
TEMPORARY_TEXT(instance_name)
CObjectModel::write_vph_identifier(gen, instance_name, kind_s);
CObjectModel::new_runtime_object(NULL, gen, I"Object", instance_name, -1, FALSE);
DISCARD_TEXT(instance_name)
}
@ And here is (iii).
=
void CObjectModel::declare_instance(code_generator *gtr, code_generation *gen,
inter_symbol *inst_s, inter_symbol *kind_s, int enumeration, segmentation_pos *ignored_saved) {
text_stream *printed_name = Metadata::optional_textual(
InterPackage::container(inst_s->definition), I"^printed_name");
int is_enumerative = FALSE;
if ((kind_s == RunningPipelines::get_symbol(gen->from_step, object_kind_RPSYM)) ||
(VanillaObjects::is_kind_of_object(gen, kind_s))) {
@<Declare an object instance@>
} else {
is_enumerative = TRUE;
CObjectModel::define_constant_for_enumeration(gen, kind_s, inst_s, enumeration);
}
int seg = (is_enumerative)?c_enum_symbols_I7CGS:c_instances_symbols_I7CGS;
segmentation_pos saved = CodeGen::select(gen, seg);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define %S %d\n",
CTarget::symbols_header_identifier(gen, I"I", printed_name), enumeration);
CodeGen::deselect(gen, saved);
}
@<Declare an object instance@> =
int c = VanillaObjects::spatial_depth(inst_s);
int is_dir = TypenameInstruction::is_a(kind_s,
RunningPipelines::get_symbol(gen->from_step, direction_kind_RPSYM));
C_property_owner *owner = CObjectModel::new_runtime_object(gtr, gen,
InterSymbol::trans(kind_s), InterSymbol::trans(inst_s), c, is_dir);
enumeration = owner->id;
@ Whether it's from case (i), (ii) or (iii), we always end up here. Note that
|acount| is negative only in cases (i) and (ii): if it is at least 0, then it
is the "arrow count", that is, its depth in the containment tree. (Calls are
made here in a hierarchical depth-first traverse of the containment tree.)
All direction objects have to be placed in the |Compass| pseudo-object.
=
C_property_owner *CObjectModel::new_runtime_object(code_generator *gtr, code_generation *gen,
text_stream *class_name, text_stream *instance_name, int acount, int is_dir) {
int id = C_GEN_DATA(objdata.owner_id_count)++;
segmentation_pos saved = CodeGen::select(gen, c_ids_and_maxima_I7CGS);
text_stream *OUT = CodeGen::current(gen);
if (Str::len(instance_name) == 0) internal_error("nameless instance");
WRITE("#define "); Generators::mangle(gen, OUT, instance_name); WRITE(" %d\n", id);
CodeGen::deselect(gen, saved);
C_property_owner *this = CObjectModel::new_owner(gen, id, instance_name, class_name, FALSE);
if (acount >= 0) @<Place this in the object containment tree@>;
return this;
}
@<Place this in the object containment tree@> =
if (acount >= MAX_C_OBJECT_TREE_DEPTH) internal_error("arrows too deep");
C_property_owner *par = NULL;
this->initial_parent = NULL;
if (acount > 0) {
par = C_GEN_DATA(objdata.arrow_chain)[acount-1];
if (par == NULL) internal_error("arrows misaligned");
} else if (is_dir) {
par = C_GEN_DATA(objdata.compass_instance);
}
if (par) {
if (par->initial_child == NULL) {
par->initial_child = this;
} else {
C_property_owner *older = par->initial_child;
while ((older) && (older->initial_sibling)) older = older->initial_sibling;
older->initial_sibling = this;
}
this->initial_parent = par;
}
C_GEN_DATA(objdata.arrow_chain)[acount] = this;
for (int i=acount+1; i<MAX_C_OBJECT_TREE_DEPTH; i++)
C_GEN_DATA(objdata.arrow_chain)[i] = NULL;
@h The property dictionary.
Each distinct property has a distinct ID. These count upwards from 0, and can
freely overlap with owner IDs or anything else. Their order is not significant.
Properties are recognised here by name, using a dictionary.
=
typedef struct C_property {
struct text_stream *name;
int id;
int either_or;
CLASS_DEFINITION
} C_property;
C_property *CObjectModel::property_by_name(code_generation *gen, text_stream *name,
int either_or) {
dictionary *D = C_GEN_DATA(objdata.declared_properties);
C_property *cp;
if (Dictionaries::find(D, name) == NULL) {
cp = CREATE(C_property);
cp->name = Str::duplicate(name);
cp->either_or = either_or;
cp->id = C_GEN_DATA(objdata.property_id_counter)++;
Dictionaries::create(D, name);
Dictionaries::write_value(D, name, (void *) cp);
} else {
cp = Dictionaries::read_value(D, name);
}
return cp;
}
@ =
C_property *CObjectModel::existing_property_by_name(code_generation *gen,
text_stream *name) {
dictionary *D = C_GEN_DATA(objdata.declared_properties);
if (Dictionaries::find(D, name) == NULL) internal_error("no such property");
return Dictionaries::read_value(D, name);
}
@h Declaring properties.
=
void CObjectModel::declare_property(code_generator *gtr, code_generation *gen,
inter_symbol *prop_s, linked_list *all_forms) {
text_stream *name = InterSymbol::trans(prop_s);
int either_or = VanillaObjects::is_either_or_property(prop_s);
C_property *cp = CObjectModel::property_by_name(gen, name, either_or);
text_stream *inner_name = VanillaObjects::inner_property_name(gen, prop_s);
@<Define the inner name as a constant@>;
@<Make the opening two metadata array entries required by Vanilla@>;
@<Define the property name in the symbols header file too@>;
}
@<Define the inner name as a constant@> =
segmentation_pos saved = CodeGen::select(gen, c_predeclarations_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define ");
Generators::mangle(gen, OUT, inner_name);
WRITE(" %d\n", cp->id);
CodeGen::deselect(gen, saved);
@ The Vanilla algorithm says we must make two array entries here, at the start
of the property's runtime metadata array. The Inform 6 generator uses the first
of those entries to say how values of this property are stored at runtime; for
C, though, all properties have the same runtime format, and we don't use the
first entry at all. We'll simply zero it.
But the second entry is the inner property, as with Inform 6.
@<Make the opening two metadata array entries required by Vanilla@> =
TEMPORARY_TEXT(val)
WRITE_TO(val, "0");
Generators::array_entry(gen, val, WORD_ARRAY_FORMAT);
Str::clear(val);
Generators::mangle(gen, val, inner_name);
Generators::array_entry(gen, val, WORD_ARRAY_FORMAT);
DISCARD_TEXT(val)
@<Define the property name in the symbols header file too@> =
text_stream *pname = Metadata::optional_textual(
InterPackage::container(prop_s->definition), I"^name");
if (Str::len(pname) > 0) {
int A = SymbolAnnotation::get_i(prop_s, C_ARRAY_ADDRESS_IANN);
if (A > 0) {
segmentation_pos saved = CodeGen::select(gen, c_property_symbols_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#define %S %d\n",
CTarget::symbols_header_identifier(gen, I"P", pname), A);
CodeGen::deselect(gen, saved);
}
}
@h Assigning properties.
Vabilla calls this to assign a property to a single owner:
=
void CObjectModel::assign_property(code_generator *gtr, code_generation *gen,
inter_symbol *prop_s, inter_pair pair, inter_tree_node *X) {
int inline_this = FALSE;
if (InterValuePairs::is_symbolic(pair)) {
inter_symbol *S = InterValuePairs::to_symbol_at(pair, X);
if (ConstantInstruction::is_inline(S)) inline_this = TRUE;
}
TEMPORARY_TEXT(val)
CodeGen::select_temporary(gen, val);
CodeGen::pair(gen, X, pair);
CodeGen::deselect_temporary(gen);
C_property_owner *owner = C_GEN_DATA(objdata.current_owner);
C_property *prop = CObjectModel::existing_property_by_name(gen,
InterSymbol::trans(prop_s));
CObjectModel::assign_one_prop(gen, owner, prop, val, inline_this);
DISCARD_TEXT(val)
}
@ And it calls this to give an array of the property's values for all of the
instances of a single enumerated kind:
=
void CObjectModel::assign_properties(code_generator *gtr, code_generation *gen,
inter_symbol *kind_s, inter_symbol *prop_s, text_stream *array) {
TEMPORARY_TEXT(mgl)
Generators::mangle(gen, mgl, array);
C_property_owner *owner = C_GEN_DATA(objdata.current_owner);
C_property *prop = CObjectModel::existing_property_by_name(gen,
InterSymbol::trans(prop_s));
CObjectModel::assign_one_prop(gen, owner, prop, mgl, FALSE);
DISCARD_TEXT(mgl)
}
@ In either case, the following assigns a property value to an owner, though
all it really does is to stash it away for now:
=
typedef struct C_pv_pair {
struct C_property *prop;
struct text_stream *val;
int inlined;
CLASS_DEFINITION
} C_pv_pair;
void CObjectModel::assign_one_prop(code_generation *gen, C_property_owner *owner,
C_property *prop, text_stream *val, int inline_this) {
C_pv_pair *pair = CREATE(C_pv_pair);
pair->prop = prop;
pair->val = Str::duplicate(val);
pair->inlined = inline_this;
ADD_TO_LINKED_LIST(pair, C_pv_pair, owner->property_values);
}
@ Creating all those //C_pv_pair//s was just playing for time, though: eventually
we have to do this --
=
void CObjectModel::write_i7_initialiser(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_initialiser_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("void i7_initialiser(i7process_t *proc) {\n"); INDENT;
WRITE("for (int id=0; id<i7_max_objects; id++) {\n"); INDENT;
WRITE("for (int p=0; p<i7_no_property_ids; p++) {\n"); INDENT;
WRITE("i7_properties[id].address[p] = 0;\n");
WRITE("i7_properties[id].len[p] = 0;\n");
OUTDENT; WRITE("}\n");
OUTDENT; WRITE("}\n");
C_property_owner *owner;
LOOP_OVER_LINKED_LIST(owner, C_property_owner, C_GEN_DATA(objdata.declared_owners)) {
C_pv_pair *vals[1024];
for (int i=0; i<1024; i++) vals[i] = NULL;
CObjectModel::gather_properties_into_array(gen, owner, vals);
for (int i=0; i<1024; i++) if (vals[i]) {
C_pv_pair *pair = vals[i];
WRITE("i7_properties[");
Generators::mangle(gen, OUT, owner->name);
WRITE("].address[i7_read_word(proc, ");
Generators::mangle(gen, OUT, pair->prop->name);
WRITE(", 1)] = ");
if (pair->inlined) {
WRITE("%S;\n", pair->val);
} else {
WRITE("%d; // %S\n", C_GEN_DATA(memdata.himem), pair->val);
CMemoryModel::array_entry(NULL, gen, pair->val, WORD_ARRAY_FORMAT);
}
WRITE("i7_properties[");
Generators::mangle(gen, OUT, owner->name);
WRITE("].len[i7_read_word(proc, ");
Generators::mangle(gen, OUT, pair->prop->name);
WRITE(", 1)] = ");
if (pair->inlined) {
WRITE("%S__xt + 1;\n", pair->val);
} else {
WRITE("1;\n");
}
}
}
OUTDENT; WRITE("}\n");
CodeGen::deselect(gen, saved);
}
void CObjectModel::gather_properties_into_array(code_generation *gen,
C_property_owner *owner, C_pv_pair **vals) {
C_property_owner *super = NULL;
C_property_owner *co;
LOOP_OVER_LINKED_LIST(co, C_property_owner, C_GEN_DATA(objdata.declared_owners)) {
if (Str::eq(co->name, owner->class)) { super = co; break; }
}
if (Str::eq(owner->name, I"Class"))
@<Ensure that Class itself has every either-or property, with the value false@>;
if (super != owner) CObjectModel::gather_properties_into_array(gen, super, vals);
C_pv_pair *pair;
LOOP_OVER_LINKED_LIST(pair, C_pv_pair, owner->property_values) {
vals[pair->prop->id] = pair;
}
}
@ The import of this is that because every owner's super-owner's super-owner...
and so on ends in |Class|, and because |Class| provides every either-or property,
it follows that every owner provides every either-or property. And in the absence
of any more specific data, it will be initially |false|.
This is not true of other properties, which have different runtime semantics.
@<Ensure that Class itself has every either-or property, with the value false@> =
if (C_GEN_DATA(objdata.Class_either_or_properties_not_set)) {
C_GEN_DATA(objdata.Class_either_or_properties_not_set) = FALSE;
C_property *prop;
LOOP_OVER(prop, C_property)
if (prop->either_or)
CObjectModel::assign_one_prop(gen, owner, prop, I"0", FALSE);
}
@h Instances which are not objects.
=
void CObjectModel::define_constant_for_enumeration(code_generation *gen,
inter_symbol *kind_s, inter_symbol *inst_s, int enumeration) {
TEMPORARY_TEXT(val)
WRITE_TO(val, "%d", enumeration);
Generators::declare_constant(gen, inst_s, RAW_GDCFORM, val);
DISCARD_TEXT(val)
}
void CObjectModel::write_vph_identifier(code_generation *gen, OUTPUT_STREAM,
inter_symbol *kind_s) {
WRITE("VPH_%d", VanillaObjects::weak_id(kind_s));
}
void CObjectModel::make_enumerated_property_arrays(code_generation *gen) {
if (C_GEN_DATA(objdata.value_ranges_needed))
@<Make the value ranges@>;
if (C_GEN_DATA(objdata.value_property_holders_needed))
@<Make the value property holders@>;
}
@ This is an array indexed by weak kind ID which holds the largest valid value
for an enumerated kind; or just 0 if the kind is not an enumeration.
@<Make the value ranges@> =
CMemoryModel::begin_array(NULL, gen, I"value_ranges", NULL, NULL, WORD_ARRAY_FORMAT, -1, NULL);
CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
inter_symbol *max_weak_id = InterSymbolsTable::URL_to_symbol(gen->from,
I"/main/synoptic/kinds/BASE_KIND_HWM");
if (max_weak_id) {
int M = InterSymbol::evaluate_to_int(max_weak_id);
for (int w=1; w<M; w++) {
int written = FALSE;
inter_symbol *kind_s;
LOOP_OVER_LINKED_LIST(kind_s, inter_symbol, gen->kinds_in_declaration_order) {
if (VanillaObjects::weak_id(kind_s) == w) {
if (VanillaObjects::value_kind_with_properties(gen, kind_s)) {
written = TRUE;
TEMPORARY_TEXT(N)
WRITE_TO(N, "%d", TypenameInstruction::instance_count(kind_s));
CMemoryModel::array_entry(NULL, gen, N, WORD_ARRAY_FORMAT);
DISCARD_TEXT(N)
}
}
}
if (written == FALSE)
CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
}
}
CMemoryModel::end_array(NULL, gen, WORD_ARRAY_FORMAT, -1, NULL);
@ This is an array indexed by weak kind ID which holds the object ID of the
value property holder for an enumerated kind; or just 0 if the kind is not an
enumeration.
@<Make the value property holders@> =
CMemoryModel::begin_array(NULL, gen, I"value_property_holders",
NULL, NULL, WORD_ARRAY_FORMAT, -1, NULL);
CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
inter_symbol *max_weak_id = InterSymbolsTable::URL_to_symbol(gen->from,
I"/main/synoptic/kinds/BASE_KIND_HWM");
if (max_weak_id) {
int M = InterSymbol::evaluate_to_int(max_weak_id);
for (int w=1; w<M; w++) {
int written = FALSE;
inter_symbol *kind_s;
LOOP_OVER_LINKED_LIST(kind_s, inter_symbol, gen->kinds_in_declaration_order) {
if (VanillaObjects::weak_id(kind_s) == w) {
if (VanillaObjects::value_kind_with_properties(gen, kind_s)) {
written = TRUE;
TEMPORARY_TEXT(N)
CObjectModel::write_vph_identifier(gen, N, kind_s);
TEMPORARY_TEXT(M)
Generators::mangle(gen, M, N);
CMemoryModel::array_entry(NULL, gen, M, WORD_ARRAY_FORMAT);
DISCARD_TEXT(M)
DISCARD_TEXT(N)
}
}
}
if (written == FALSE)
CMemoryModel::array_entry(NULL, gen, I"0", WORD_ARRAY_FORMAT);
}
}
CMemoryModel::end_array(NULL, gen, WORD_ARRAY_FORMAT, -1, NULL);
@h Primitives.
The following primitives are all implemented by calling suitable C functions,
which we will then need to write in |inform7_clib.h|.
For |i7_metaclass|, see //CObjectModel::define_object_value_regions// above.
=
int CObjectModel::invoke_primitive(code_generation *gen, inter_ti bip, inter_tree_node *P) {
text_stream *OUT = CodeGen::current(gen);
switch (bip) {
case PROPERTYARRAY_BIP:
WRITE("i7_prop_addr(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
VNODE_3C; WRITE(")"); break;
case PROPERTYLENGTH_BIP:
WRITE("i7_prop_len(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
VNODE_3C; WRITE(")"); break;
case MOVE_BIP:
WRITE("i7_move(proc, "); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); break;
case REMOVE_BIP:
WRITE("i7_move(proc, "); VNODE_1C; WRITE(", 0)"); break;
case CHILD_BIP:
WRITE("i7_child(proc, "); VNODE_1C; WRITE(")"); break;
case CHILDREN_BIP:
WRITE("i7_children(proc, "); VNODE_1C; WRITE(")"); break;
case PARENT_BIP:
WRITE("i7_parent(proc, "); VNODE_1C; WRITE(")"); break;
case SIBLING_BIP:
WRITE("i7_sibling(proc, "); VNODE_1C; WRITE(")"); break;
case METACLASS_BIP:
WRITE("i7_metaclass(proc, "); VNODE_1C; WRITE(")"); break;
default: return NOT_APPLICABLE;
}
return FALSE;
}
@ Let's start with property address and property length; while actual property
values live inside process memory, the addresses showing where they are in that
memory, and how many bytes they take up, are held in (static) arrays. Note that
although multiple processes running the same I7 story would have multiple values
for these properties, which likely differ at any given time, they would be at
the same address and of the same length in each.
= (text to inform7_clib.h)
#define I7_MAX_PROPERTY_IDS 1000
typedef struct i7_property_set {
i7word_t address[I7_MAX_PROPERTY_IDS];
i7word_t len[I7_MAX_PROPERTY_IDS];
} i7_property_set;
extern i7_property_set i7_properties[];
i7word_t i7_prop_addr(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr);
i7word_t i7_prop_len(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr);
=
Lengths are returned in bytes, not words, hence the multiplication by 4.
= (text to inform7_clib.c)
i7_property_set i7_properties[i7_max_objects];
i7word_t i7_prop_len(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr_array) {
i7word_t pr = i7_read_word(proc, pr_array, 1);
if ((obj <= 0) || (obj >= i7_max_objects) ||
(pr < 0) || (pr >= i7_no_property_ids)) return 0;
return 4*i7_properties[(int) obj].len[(int) pr];
}
i7word_t i7_prop_addr(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr_array) {
i7word_t pr = i7_read_word(proc, pr_array, 1);
if ((obj <= 0) || (obj >= i7_max_objects) ||
(pr < 0) || (pr >= i7_no_property_ids)) return 0;
return i7_properties[(int) obj].address[(int) pr];
}
@ The address array can be used to determine whether a runtime object or class
provides a given property: if the address is nonzero then it does.
= (text to inform7_clib.h)
int i7_provides(i7process_t *proc, i7word_t owner_id, i7word_t prop_id);
=
= (text to inform7_clib.c)
int i7_provides(i7process_t *proc, i7word_t owner_id, i7word_t pr_array) {
i7word_t prop_id = i7_read_word(proc, pr_array, 1);
if ((owner_id <= 0) || (owner_id >= i7_max_objects) ||
(prop_id < 0) || (prop_id >= i7_no_property_ids)) return 0;
while (owner_id != 1) {
if (i7_properties[(int) owner_id].address[(int) prop_id] != 0) return 1;
owner_id = i7_class_of[owner_id];
}
return 0;
}
=
@ Now |i7_move|, which moves |obj| in the object tree so that it becomes the
eldest child of |to|, unless |to| is zero, in which case it is removed from
the tree.
= (text to inform7_clib.h)
void i7_move(i7process_t *proc, i7word_t obj, i7word_t to);
=
= (text to inform7_clib.c)
void i7_move(i7process_t *proc, i7word_t obj, i7word_t to) {
if ((obj <= 0) || (obj >= i7_max_objects)) return;
int p = proc->state.object_tree_parent[obj];
if (p) {
if (proc->state.object_tree_child[p] == obj) {
proc->state.object_tree_child[p] = proc->state.object_tree_sibling[obj];
} else {
int c = proc->state.object_tree_child[p];
while (c != 0) {
if (proc->state.object_tree_sibling[c] == obj) {
proc->state.object_tree_sibling[c] = proc->state.object_tree_sibling[obj];
break;
}
c = proc->state.object_tree_sibling[c];
}
}
}
proc->state.object_tree_parent[obj] = to;
proc->state.object_tree_sibling[obj] = 0;
if (to) {
proc->state.object_tree_sibling[obj] = proc->state.object_tree_child[to];
proc->state.object_tree_child[to] = obj;
}
}
=
@ Now the four ways to interrogate the object containment tree:
= (text to inform7_clib.h)
i7word_t i7_parent(i7process_t *proc, i7word_t id);
i7word_t i7_child(i7process_t *proc, i7word_t id);
i7word_t i7_children(i7process_t *proc, i7word_t id);
i7word_t i7_sibling(i7process_t *proc, i7word_t id);
=
= (text to inform7_clib.c)
i7word_t i7_parent(i7process_t *proc, i7word_t id) {
if (i7_metaclass(proc, id) != i7_mgl_Object) return 0;
return proc->state.object_tree_parent[id];
}
i7word_t i7_child(i7process_t *proc, i7word_t id) {
if (i7_metaclass(proc, id) != i7_mgl_Object) return 0;
return proc->state.object_tree_child[id];
}
i7word_t i7_children(i7process_t *proc, i7word_t id) {
if (i7_metaclass(proc, id) != i7_mgl_Object) return 0;
i7word_t c=0;
for (int i=0; i<i7_max_objects; i++)
if (proc->state.object_tree_parent[i] == id)
c++;
return c;
}
i7word_t i7_sibling(i7process_t *proc, i7word_t id) {
if (i7_metaclass(proc, id) != i7_mgl_Object) return 0;
return proc->state.object_tree_sibling[id];
}
=
@ And the implementation of "is |obj1| directly a child of |obj2|?"
= (text to inform7_clib.h)
int i7_in(i7process_t *proc, i7word_t obj1, i7word_t obj2);
=
= (text to inform7_clib.c)
int i7_in(i7process_t *proc, i7word_t obj1, i7word_t obj2) {
if (i7_metaclass(proc, obj1) != i7_mgl_Object) return 0;
if (obj2 == 0) return 0;
if (proc->state.object_tree_parent[obj1] == obj2) return 1;
return 0;
}
=
@h Reading, writing and changing object properties.
= (text to inform7_clib.h)
i7word_t i7_read_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t pr_array);
void i7_write_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t prop_id, i7word_t val);
i7word_t i7_change_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t prop_id,
i7word_t val, int way);
=
= (text to inform7_clib.c)
i7word_t i7_read_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t pr_array) {
i7word_t prop_id = i7_read_word(proc, pr_array, 1);
if ((owner_id <= 0) || (owner_id >= i7_max_objects) ||
(prop_id < 0) || (prop_id >= i7_no_property_ids)) return 0;
while (i7_properties[(int) owner_id].address[(int) prop_id] == 0) {
owner_id = i7_class_of[owner_id];
if (owner_id == i7_mgl_Class) return 0;
}
i7word_t address = i7_properties[(int) owner_id].address[(int) prop_id];
return i7_read_word(proc, address, 0);
}
void i7_write_prop_value(i7process_t *proc, i7word_t owner_id, i7word_t pr_array, i7word_t val) {
i7word_t prop_id = i7_read_word(proc, pr_array, 1);
if ((owner_id <= 0) || (owner_id >= i7_max_objects) ||
(prop_id < 0) || (prop_id >= i7_no_property_ids)) {
printf("impossible property write (%d, %d)\n", owner_id, prop_id);
i7_fatal_exit(proc);
}
i7word_t address = i7_properties[(int) owner_id].address[(int) prop_id];
if (address) i7_write_word(proc, address, 0, val);
else {
printf("impossible property write (%d, %d)\n", owner_id, prop_id);
i7_fatal_exit(proc);
}
}
i7word_t i7_change_prop_value(i7process_t *proc, i7word_t obj, i7word_t pr,
i7word_t to, int way) {
i7word_t val = i7_read_prop_value(proc, obj, pr), new_val = val;
switch (way) {
case i7_lvalue_SET:
i7_write_prop_value(proc, obj, pr, to); new_val = to; break;
case i7_lvalue_PREDEC:
new_val = val-1; i7_write_prop_value(proc, obj, pr, val-1); break;
case i7_lvalue_POSTDEC:
new_val = val; i7_write_prop_value(proc, obj, pr, val-1); break;
case i7_lvalue_PREINC:
new_val = val+1; i7_write_prop_value(proc, obj, pr, val+1); break;
case i7_lvalue_POSTINC:
new_val = val; i7_write_prop_value(proc, obj, pr, val+1); break;
case i7_lvalue_SETBIT:
new_val = val | new_val; i7_write_prop_value(proc, obj, pr, new_val); break;
case i7_lvalue_CLEARBIT:
new_val = val &(~new_val); i7_write_prop_value(proc, obj, pr, new_val); break;
}
return new_val;
}
=
@h Reading, writing and changing general properties.
And these are the exactly analogous functions which more generally read, write
or change properties which can be held by either objects or enumerated instances --
in other words, all properties. The additional kind argument |K| is then needed
to distinguish these cases (since the |obj| values for different kinds may well
coincide).
The functions themselves are simple enough, but there is a complication, which
is that they need to use addresses which vary from one compilation to another;
so they cannot be written straightforwardly into our C library, which has to
be the same for all compilations. We get around this by compiling wrapper
functions in our story-file C which supply the necessary information and then
call clumsy but static functions in the C library; but this is all transparent
to the user, who should call only these:
= (text to inform7_clib.h)
int i7_provides_gprop(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p);
i7word_t i7_read_gprop_value(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr);
void i7_write_gprop_value(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p,
i7word_t val);
void i7_change_gprop_value(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p,
i7word_t val, i7word_t form);
=
@ So here are the dynamic wrappers.
=
void CObjectModel::compile_gprop_functions(code_generation *gen) {
segmentation_pos saved = CodeGen::select(gen, c_function_declarations_I7CGS);
text_stream *OUT = CodeGen::current(gen);
WRITE("#ifdef i7_mgl_OBJECT_TY\n");
WRITE("int i7_provides_gprop(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p) {\n");
WRITE(" return i7_provides_gprop_inner(proc, K, obj, p, i7_mgl_OBJECT_TY,\n");
WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n");
WRITE("}\n");
WRITE("i7word_t i7_read_gprop_value(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p) {\n");
WRITE(" return i7_read_gprop_value_inner(proc, K, obj, p, i7_mgl_OBJECT_TY,\n");
WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n");
WRITE("}\n");
WRITE("void i7_write_gprop_value(i7process_t *proc, i7word_t K, i7word_t obj,\n");
WRITE(" i7word_t p, i7word_t val) {\n");
WRITE(" i7_write_gprop_value_inner(proc, K, obj, p, val, i7_mgl_OBJECT_TY,\n");
WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n");
WRITE("}\n");
WRITE("void i7_change_gprop_value(i7process_t *proc, i7word_t K, i7word_t obj,\n");
WRITE(" i7word_t p, i7word_t val, i7word_t form) {\n");
WRITE(" i7_change_gprop_value_inner(proc, K, obj, p, val, form, i7_mgl_OBJECT_TY,\n");
WRITE(" i7_mgl_value_ranges, i7_mgl_value_property_holders, i7_mgl_COL_HSIZE);\n");
WRITE("}\n");
WRITE("#endif\n");
CodeGen::deselect(gen, saved);
}
@ And these are the static functions in the C library which they call:
= (text to inform7_clib.h)
int i7_provides_gprop_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p,
i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE);
i7word_t i7_read_gprop_value_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr,
i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE);
void i7_write_gprop_value_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p,
i7word_t val, i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE);
void i7_change_gprop_value_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t p,
i7word_t val, i7word_t form, i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE);
=
= (text to inform7_clib.c)
int i7_provides_gprop_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr,
i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE) {
if (K == i7_mgl_OBJECT_TY) {
if ((((obj) && ((i7_metaclass(proc, obj) == i7_mgl_Object)))) &&
(((i7_read_word(proc, pr, 0) == 2) || (i7_provides(proc, obj, pr)))))
return 1;
} else {
if ((((obj >= 1)) && ((obj <= i7_read_word(proc, i7_mgl_value_ranges, K))))) {
i7word_t holder = i7_read_word(proc, i7_mgl_value_property_holders, K);
if (((holder) && ((i7_provides(proc, holder, pr))))) return 1;
}
}
return 0;
}
i7word_t i7_read_gprop_value_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr,
i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE) {
i7word_t val = 0;
if ((K == i7_mgl_OBJECT_TY)) {
return (i7word_t) i7_read_prop_value(proc, obj, pr);
} else {
i7word_t holder = i7_read_word(proc, i7_mgl_value_property_holders, K);
return (i7word_t) i7_read_word(proc,
i7_read_prop_value(proc, holder, pr), (obj + i7_mgl_COL_HSIZE));
}
return val;
}
void i7_write_gprop_value_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr,
i7word_t val, i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE) {
if ((K == i7_mgl_OBJECT_TY)) {
i7_write_prop_value(proc, obj, pr, val);
} else {
i7word_t holder = i7_read_word(proc, i7_mgl_value_property_holders, K);
i7_write_word(proc,
i7_read_prop_value(proc, holder, pr), (obj + i7_mgl_COL_HSIZE), val);
}
}
void i7_change_gprop_value_inner(i7process_t *proc, i7word_t K, i7word_t obj, i7word_t pr,
i7word_t val, i7word_t form,
i7word_t i7_mgl_OBJECT_TY, i7word_t i7_mgl_value_ranges,
i7word_t i7_mgl_value_property_holders, i7word_t i7_mgl_COL_HSIZE) {
if ((K == i7_mgl_OBJECT_TY)) {
i7_change_prop_value(proc, obj, pr, val, form);
} else {
i7word_t holder = i7_read_word(proc, i7_mgl_value_property_holders, K);
i7_change_word(proc,
i7_read_prop_value(proc, holder, pr), (obj + i7_mgl_COL_HSIZE), val, form);
}
}
=