mirror of
https://github.com/ganelson/inform.git
synced 2024-07-08 01:54:21 +03:00
1022 lines
42 KiB
OpenEdge ABL
1022 lines
42 KiB
OpenEdge ABL
[I6TargetCode::] Inform 6 Code.
|
|
|
|
To generate I6 routines of imperative code.
|
|
|
|
@ =
|
|
void I6TargetCode::create_generator(code_generator *cgt) {
|
|
METHOD_ADD(cgt, DECLARE_FUNCTION_MTID, I6TargetCode::declare_function);
|
|
METHOD_ADD(cgt, PLACE_LABEL_MTID, I6TargetCode::place_label);
|
|
METHOD_ADD(cgt, EVALUATE_LABEL_MTID, I6TargetCode::evaluate_label);
|
|
METHOD_ADD(cgt, INVOKE_PRIMITIVE_MTID, I6TargetCode::invoke_primitive);
|
|
METHOD_ADD(cgt, INVOKE_FUNCTION_MTID, I6TargetCode::invoke_function);
|
|
METHOD_ADD(cgt, INVOKE_OPCODE_MTID, I6TargetCode::invoke_opcode);
|
|
}
|
|
|
|
@h Functions.
|
|
Inform 6 originated as an assembler briefly called "zass", and its assembly-language
|
|
character can still be seen in the way functions are declared:
|
|
= (text as Inform 6)
|
|
[ FunctionName local1 local2 local3 ... localn;
|
|
...
|
|
];
|
|
=
|
|
Here |local1|, |local2|, ..., |localn| are all of the local variables accessible
|
|
from the function; the earliest will be used as call parameters, all subsequent
|
|
ones being initially zero.
|
|
|
|
=
|
|
void I6TargetCode::declare_function(code_generator *cgt, code_generation *gen,
|
|
inter_symbol *fn, inter_tree_node *D, vanilla_function *fcf) {
|
|
segmentation_pos saved = CodeGen::select(gen, functions_I7CGS);
|
|
text_stream *fn_name = Inter::Symbols::name(fn);
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
@<Open the function@>;
|
|
|
|
if (Str::eq(fn_name, I"Main")) @<Inject code at the top of Main@>;
|
|
if (Str::eq(fn_name, I"DebugAction")) @<Inject code at the top of DebugAction@>;
|
|
if (Str::eq(fn_name, I"DebugAttribute")) @<Inject code at the top of DebugAttribute@>;
|
|
if (Str::eq(fn_name, I"DebugProperty")) @<Inject code at the top of DebugProperty@>;
|
|
if (Str::eq(fn_name, I"FINAL_CODE_STARTUP_R")) @<Inject code at the top of FINAL_CODE_STARTUP_R@>;
|
|
|
|
Vanilla::node(gen, D); /* This compiles the body of the function */
|
|
|
|
@<Close the function@>;
|
|
CodeGen::deselect(gen, saved);
|
|
}
|
|
|
|
@<Open the function@> =
|
|
WRITE("[ %S", fn_name);
|
|
I6TargetCode::seek_locals(gen, D);
|
|
WRITE(";");
|
|
|
|
@ =
|
|
void I6TargetCode::seek_locals(code_generation *gen, inter_tree_node *P) {
|
|
if (P->W.data[ID_IFLD] == LOCAL_IST) {
|
|
inter_package *pack = Inter::Packages::container(P);
|
|
inter_symbol *var_name =
|
|
InterSymbolsTables::local_symbol_from_id(pack, P->W.data[DEFN_LOCAL_IFLD]);
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
WRITE(" %S", var_name->symbol_name);
|
|
}
|
|
LOOP_THROUGH_INTER_CHILDREN(F, P) I6TargetCode::seek_locals(gen, F);
|
|
}
|
|
|
|
@<Close the function@> =
|
|
WRITE("];\n");
|
|
|
|
@ A few functions will be sneakily rewritten in passing. This is done to handle
|
|
specific features of the Z or Glulx virtual machines which do not meaningfully
|
|
exist in any wider cross-platform way. Although this could all be done by having
|
|
a slightly more elaborate linker and then including the code below in kits
|
|
(as was indeed done during 2020), it's really better that the Inter tree not
|
|
have to refer to eldritch Z-only symbols like |#largest_object| or Glulx-only
|
|
symbols like |#g$self|.
|
|
|
|
@<Inject code at the top of Main@> =
|
|
WRITE("#ifdef TARGET_ZCODE; max_z_object = #largest_object - 255; #endif;\n");
|
|
|
|
@<Inject code at the top of DebugAction@> =
|
|
WRITE("#ifdef TARGET_GLULX;\n");
|
|
WRITE("if (a < 4096) {\n");
|
|
WRITE(" if (a < 0 || a >= #identifiers_table-->7) print \"<invalid action \", a, \">\";\n");
|
|
WRITE(" else {\n");
|
|
WRITE(" str = #identifiers_table-->6;\n");
|
|
WRITE(" str = str-->a;\n");
|
|
WRITE(" if (str) print (string) str; else print \"<unnamed action \", a, \">\";\n");
|
|
WRITE(" return;\n");
|
|
WRITE(" }\n");
|
|
WRITE("}\n");
|
|
WRITE("#endif;\n");
|
|
WRITE("#ifdef TARGET_ZCODE;\n");
|
|
WRITE("if (a < 4096) {\n");
|
|
WRITE(" anames = #identifiers_table;\n");
|
|
WRITE(" anames = anames + 2*(anames-->0) + 2*48;\n");
|
|
WRITE(" print (string) anames-->a;\n");
|
|
WRITE(" return;\n");
|
|
WRITE("}\n");
|
|
WRITE("#endif;\n");
|
|
|
|
@<Inject code at the top of DebugAttribute@> =
|
|
I6_GEN_DATA(DebugAttribute_seen) = TRUE;
|
|
WRITE("#ifdef TARGET_GLULX;\n");
|
|
WRITE("if (a < 0 || a >= NUM_ATTR_BYTES*8) print \"<invalid attribute \", a, \">\";\n");
|
|
WRITE("else {\n");
|
|
WRITE(" str = #identifiers_table-->4;\n");
|
|
WRITE(" str = str-->a;\n");
|
|
WRITE(" if (str) print (string) str; else print \"<unnamed attribute \", a, \">\";\n");
|
|
WRITE("}\n");
|
|
WRITE("return;\n");
|
|
WRITE("#endif;\n");
|
|
WRITE("#ifdef TARGET_ZCODE;\n");
|
|
WRITE("if (a < 0 || a >= 48) print \"<invalid attribute \", a, \">\";\n");
|
|
WRITE("else {\n");
|
|
WRITE(" anames = #identifiers_table; anames = anames + 2*(anames-->0);\n");
|
|
WRITE(" print (string) anames-->a;\n");
|
|
WRITE("}\n");
|
|
WRITE("return;\n");
|
|
WRITE("#endif;\n");
|
|
|
|
@<Inject code at the top of DebugProperty@> =
|
|
WRITE("print (property) p;\n");
|
|
WRITE("return;\n");
|
|
|
|
@ This enables a speed optimisation in the Glulx virtual machine which
|
|
reimplements some of the veneer functions in "hardware". If it weren't here,
|
|
or if the Gestalt said that the VM didn't support this after all, no harm would
|
|
be done except for a slight slowdown.
|
|
|
|
@<Inject code at the top of FINAL_CODE_STARTUP_R@> =
|
|
WRITE("#ifdef TARGET_GLULX;\n");
|
|
WRITE("@gestalt 9 0 res;\n");
|
|
WRITE("if (res == 0) rfalse;\n");
|
|
WRITE("addr = #classes_table;\n");
|
|
WRITE("@accelparam 0 addr;\n");
|
|
WRITE("@accelparam 1 INDIV_PROP_START;\n");
|
|
WRITE("@accelparam 2 Class;\n");
|
|
WRITE("@accelparam 3 Object;\n");
|
|
WRITE("@accelparam 4 Routine;\n");
|
|
WRITE("@accelparam 5 String;\n");
|
|
WRITE("addr = #globals_array + WORDSIZE * #g$self;\n");
|
|
WRITE("@accelparam 6 addr;\n");
|
|
WRITE("@accelparam 7 NUM_ATTR_BYTES;\n");
|
|
WRITE("addr = #cpv__start;\n");
|
|
WRITE("@accelparam 8 addr;\n");
|
|
WRITE("@accelfunc 1 Z__Region;\n");
|
|
WRITE("@accelfunc 2 CP__Tab;\n");
|
|
WRITE("@accelfunc 3 RA__Pr;\n");
|
|
WRITE("@accelfunc 4 RL__Pr;\n");
|
|
WRITE("@accelfunc 5 OC__Cl;\n");
|
|
WRITE("@accelfunc 6 RV__Pr;\n");
|
|
WRITE("@accelfunc 7 OP__Pr;\n");
|
|
WRITE("#endif;\n");
|
|
WRITE("rfalse;\n");
|
|
|
|
@h Labels.
|
|
Labels in Inform 6 are |jump| destinations, much as in C they are |goto| destinations.
|
|
A full stop indicates where they are positioned:
|
|
= (text as Inform 6)
|
|
if (whatever) jump Catastrophe;
|
|
...
|
|
.Catastrophe;
|
|
...
|
|
=
|
|
Inter identifiers for labels also start with full stops. So:
|
|
|
|
=
|
|
void I6TargetCode::place_label(code_generator *cgt, code_generation *gen,
|
|
text_stream *label_name) {
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
WRITE("%S;\n", label_name);
|
|
}
|
|
void I6TargetCode::evaluate_label(code_generator *cgt, code_generation *gen,
|
|
text_stream *label_name) {
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
LOOP_THROUGH_TEXT(pos, label_name)
|
|
if (Str::get(pos) != '.')
|
|
PUT(Str::get(pos));
|
|
}
|
|
|
|
@h Function invocations.
|
|
Or in other words, function calls. These are easy: the syntax is exactly what
|
|
it would be for C.
|
|
|
|
=
|
|
void I6TargetCode::invoke_function(code_generator *cgt, code_generation *gen,
|
|
inter_symbol *fn, inter_tree_node *P, vanilla_function *fcf, int void_context) {
|
|
text_stream *fn_name = Inter::Symbols::name(fn);
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
WRITE("%S(", fn_name);
|
|
int c = 0;
|
|
LOOP_THROUGH_INTER_CHILDREN(F, P) {
|
|
if (c++ > 0) WRITE(", ");
|
|
Vanilla::node(gen, F);
|
|
}
|
|
WRITE(")");
|
|
if (void_context) WRITE(";\n");
|
|
}
|
|
|
|
@h Assembly language.
|
|
In general, we make no attempt to police the supposedly valid assembly language
|
|
given to us here. Glulx has one set, Z another. Any assembly language in the Inter
|
|
tree results from kit material; and if the author of such a kit tries to use an
|
|
invalid opcode, then the result won't compile under I6, but none of that is our
|
|
business here.
|
|
|
|
The |@aread| opcode is a valid Z-machine opcode, but owing to the way I6 handles
|
|
the irreconcilable change in syntax for the same opcode in V3 and V4-5 of the
|
|
Z-machine specification, there is no good way to assemble it using |@| notation
|
|
unless we want to save the result. (See the Z-Machine Standards Document.)
|
|
As a dodge, we use the Inform 6 statement |read X Y| instead.
|
|
|
|
=
|
|
void I6TargetCode::invoke_opcode(code_generator *cgt, code_generation *gen,
|
|
text_stream *opcode, int operand_count, inter_tree_node **operands,
|
|
inter_tree_node *label, int label_sense) {
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
if (Str::eq(opcode, I"@aread")) WRITE("read");
|
|
else WRITE("%S", opcode);
|
|
for (int opc = 0; opc < operand_count; opc++) {
|
|
WRITE(" ");
|
|
Vanilla::node(gen, operands[opc]);
|
|
}
|
|
if (label) {
|
|
WRITE(" ?");
|
|
if (label_sense == FALSE) WRITE("~");
|
|
Vanilla::node(gen, label);
|
|
}
|
|
WRITE(";\n");
|
|
}
|
|
|
|
@h Primitives.
|
|
|
|
=
|
|
void I6TargetCode::invoke_primitive(code_generator *cgt, code_generation *gen,
|
|
inter_symbol *prim_name, inter_tree_node *P, int void_context) {
|
|
inter_tree *I = gen->from;
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
inter_ti bip = Primitives::to_bip(I, prim_name);
|
|
|
|
int suppress_terminal_semicolon = (void_context)?FALSE:TRUE;
|
|
switch (bip) {
|
|
@<Basic arithmetic and logical operations@>;
|
|
@<Storing or otherwise changing values@>;
|
|
@<VM stack access@>;
|
|
@<Control structures@>;
|
|
@<Indirect function calls@>;
|
|
@<Method calls@>;
|
|
@<Property value access@>;
|
|
@<Textual output@>;
|
|
@<The VM object tree@>;
|
|
default:
|
|
WRITE_TO(STDERR, "Unimplemented primitive is '%S'\n", prim_name->symbol_name);
|
|
internal_error("unimplemented prim");
|
|
}
|
|
if (suppress_terminal_semicolon == FALSE) WRITE(";\n");
|
|
}
|
|
|
|
@ Mostly easy, because the Inter primitives here were so closely modelled on
|
|
their Inform 6 analogues in the first place.
|
|
|
|
For example, although |!alternative| is a very unusual linguistic feature --
|
|
it allows alternatives in several conditions, e.g., |if (x == 1 or 2 or 3) ...| --
|
|
it corresponds directly to the |or| keyword of Inform 6, so generating it is trivial.
|
|
|
|
@<Basic arithmetic and logical operations@> =
|
|
case PLUS_BIP: WRITE("("); VNODE_1C; WRITE(" + "); VNODE_2C; WRITE(")"); break;
|
|
case MINUS_BIP: WRITE("("); VNODE_1C; WRITE(" - "); VNODE_2C; WRITE(")"); break;
|
|
case UNARYMINUS_BIP: WRITE("(-("); VNODE_1C; WRITE("))"); break;
|
|
case TIMES_BIP: WRITE("("); VNODE_1C; WRITE("*"); VNODE_2C; WRITE(")"); break;
|
|
case DIVIDE_BIP: WRITE("("); VNODE_1C; WRITE("/"); VNODE_2C; WRITE(")"); break;
|
|
case MODULO_BIP: WRITE("("); VNODE_1C; WRITE("%%"); VNODE_2C; WRITE(")"); break;
|
|
case BITWISEAND_BIP: WRITE("(("); VNODE_1C; WRITE(")&("); VNODE_2C; WRITE("))"); break;
|
|
case BITWISEOR_BIP: WRITE("(("); VNODE_1C; WRITE(")|("); VNODE_2C; WRITE("))"); break;
|
|
case BITWISENOT_BIP: WRITE("(~("); VNODE_1C; WRITE("))"); break;
|
|
case NOT_BIP: WRITE("(~~("); VNODE_1C; WRITE("))"); break;
|
|
case AND_BIP: WRITE("(("); VNODE_1C; WRITE(") && ("); VNODE_2C; WRITE("))"); break;
|
|
case OR_BIP: WRITE("(("); VNODE_1C; WRITE(") || ("); VNODE_2C; WRITE("))"); break;
|
|
case EQ_BIP: WRITE("("); VNODE_1C; WRITE(" == "); VNODE_2C; WRITE(")"); break;
|
|
case NE_BIP: WRITE("("); VNODE_1C; WRITE(" ~= "); VNODE_2C; WRITE(")"); break;
|
|
case GT_BIP: WRITE("("); VNODE_1C; WRITE(" > "); VNODE_2C; WRITE(")"); break;
|
|
case GE_BIP: WRITE("("); VNODE_1C; WRITE(" >= "); VNODE_2C; WRITE(")"); break;
|
|
case LT_BIP: WRITE("("); VNODE_1C; WRITE(" < "); VNODE_2C; WRITE(")"); break;
|
|
case LE_BIP: WRITE("("); VNODE_1C; WRITE(" <= "); VNODE_2C; WRITE(")"); break;
|
|
case OFCLASS_BIP: WRITE("("); VNODE_1C; WRITE(" ofclass "); VNODE_2C; WRITE(")"); break;
|
|
case IN_BIP: WRITE("("); VNODE_1C; WRITE(" in "); VNODE_2C; WRITE(")"); break;
|
|
case NOTIN_BIP: WRITE("("); VNODE_1C; WRITE(" notin "); VNODE_2C; WRITE(")"); break;
|
|
case LOOKUP_BIP: WRITE("("); VNODE_1C; WRITE("-->("); VNODE_2C; WRITE("))"); break;
|
|
case LOOKUPBYTE_BIP: WRITE("("); VNODE_1C; WRITE("->("); VNODE_2C; WRITE("))"); break;
|
|
case ALTERNATIVE_BIP: VNODE_1C; WRITE(" or "); VNODE_2C; break;
|
|
case SEQUENTIAL_BIP: WRITE("("); VNODE_1C; WRITE(","); VNODE_2C; WRITE(")"); break;
|
|
case TERNARYSEQUENTIAL_BIP: @<Generate primitive for ternarysequential@>; break;
|
|
|
|
@ But the unfortunate |!ternarysequential a b c| needs some gymnastics. It
|
|
would be trivial to generate to C with the serial comma operator: |(a, b, c)|
|
|
evaluates |a|, then throws that away and evaluates |b|, then throws that away
|
|
too and returns the value of |c|.
|
|
|
|
The same effect is annoyingly difficult to get out of the sometimes shaky I6
|
|
compiler's expression parser. I6 does support the comma operator, so at first
|
|
sight |(a, b, c)| ought to work in I6, too. And it does, right up to the point
|
|
where some of the token values themselves include invocations of functions. It
|
|
is a known infelicity of the I6 syntax analyser that it won't always allow the
|
|
serial comma to be mixed in the same expression with the function argument
|
|
comma: for example in the case |(a(b, c), d)|, where the first comma constructs
|
|
a list of arguments and the second is the operator. (Many such expressions work
|
|
fine in I6 -- but not all.)
|
|
|
|
That being so, we use the following circumlocution:
|
|
= (text as Inform 6)
|
|
(c) + 0*((b) + (a))
|
|
=
|
|
Because I6 evaluates the leaves in an expression tree right-to-left, not
|
|
left-to-right, the parameter assignments happen first, then the conditions,
|
|
then the result.
|
|
|
|
@<Generate primitive for ternarysequential@> =
|
|
WRITE("(\n"); INDENT;
|
|
WRITE("! This evaluates last\n"); VNODE_3C;
|
|
OUTDENT; WRITE("+\n"); INDENT;
|
|
WRITE("0*(\n"); INDENT;
|
|
WRITE("! This evaluates second\n");
|
|
WRITE("((\n"); INDENT; VNODE_2C;
|
|
OUTDENT; WRITE("\n))\n");
|
|
OUTDENT; WRITE("+\n"); INDENT;
|
|
WRITE("! This evaluate first\n");
|
|
WRITE("("); VNODE_1C; WRITE(")");
|
|
OUTDENT; WRITE(")\n");
|
|
OUTDENT; WRITE(")\n");
|
|
|
|
@ These are the seven primitives which change a storage item given by a
|
|
reference, which is always the first child of the primitive node. It might,
|
|
for example, be a global variable, or a memory location.
|
|
|
|
@<Storing or otherwise changing values@> =
|
|
case STORE_BIP: @<Perform a store@>; break;
|
|
case PREINCREMENT_BIP: @<Perform a store@>; break;
|
|
case POSTINCREMENT_BIP: @<Perform a store@>; break;
|
|
case PREDECREMENT_BIP: @<Perform a store@>; break;
|
|
case POSTDECREMENT_BIP: @<Perform a store@>; break;
|
|
case SETBIT_BIP: @<Perform a store@>; break;
|
|
case CLEARBIT_BIP: @<Perform a store@>; break;
|
|
|
|
@<Perform a store@> =
|
|
inter_tree_node *storage_ref = InterTree::first_child(P);
|
|
if (storage_ref->W.data[0] == REFERENCE_IST)
|
|
storage_ref = InterTree::first_child(storage_ref);
|
|
if ((Inter::Reference::node_is_ref_to(gen->from, InterTree::first_child(P),
|
|
PROPERTYVALUE_BIP)) &&
|
|
(I6TargetCode::pval_case(storage_ref) != I6G_CAN_PROVE_IS_OBJ_PROPERTY)) {
|
|
@<Alter a property value@>;
|
|
} else {
|
|
@<Alter some other storage@>;
|
|
}
|
|
|
|
@ The easy case first: here, whatever the storage is (for example, a variable),
|
|
it's one that we can simply treat as an lvalue in Inform 6 (for example, by giving
|
|
its variable name). For example, the memory location |A-->3| can be assigned to,
|
|
or can have |++| or |--| applied to it in I6.
|
|
|
|
Note that this case even includes some property values: if we can see that |P|
|
|
is the explicit name of a property we are storing in a VM-property, then we can
|
|
use |O.P| as an Inform 6 lvalue, and all is well, and we then end up with code
|
|
such as |++(O.P)|.
|
|
|
|
@<Alter some other storage@> =
|
|
switch (bip) {
|
|
case PREINCREMENT_BIP: WRITE("++("); VNODE_1C; WRITE(")"); break;
|
|
case POSTINCREMENT_BIP: WRITE("("); VNODE_1C; WRITE(")++"); break;
|
|
case PREDECREMENT_BIP: WRITE("--("); VNODE_1C; WRITE(")"); break;
|
|
case POSTDECREMENT_BIP: WRITE("("); VNODE_1C; WRITE(")--"); break;
|
|
case STORE_BIP: WRITE("("); VNODE_1C; WRITE(" = "); VNODE_2C; WRITE(")"); break;
|
|
case SETBIT_BIP: VNODE_1C; WRITE(" = "); VNODE_1C; WRITE(" | "); VNODE_2C; break;
|
|
case CLEARBIT_BIP: VNODE_1C; WRITE(" = "); VNODE_1C; WRITE(" &~ ("); VNODE_2C; WRITE(")"); break;
|
|
}
|
|
|
|
@ But not all property values can be written as Inform 6 lvalues. If the I7
|
|
property P is being stored as a VM-attribute A, then there is no lvalue which
|
|
expresses the value of A for an object O: instead one must use |give O A| to
|
|
set it, |give O ~A| to unset it, and |(O has A)| to test it. And there will
|
|
also be cases where P cannot be identified at compile-time, so that we have no
|
|
way to know whether it will be stored as a VM-attribute or not.
|
|
|
|
To handle these two cases, then, we will compile an attempt to store or modify
|
|
a property value either as a |give| statement -- if we can prove P is being
|
|
stored in a VM-attribute -- or else as a function call to a general-purpose
|
|
function called |_final_change_property|.
|
|
|
|
@<Alter a property value@> =
|
|
inter_tree_node *VP = InterTree::second_child(P);
|
|
int set = NOT_APPLICABLE;
|
|
if (VP->W.data[ID_IFLD] == VAL_IST) {
|
|
inter_ti val1 = VP->W.data[VAL1_VAL_IFLD];
|
|
inter_ti val2 = VP->W.data[VAL2_VAL_IFLD];
|
|
if ((val1 == LITERAL_IVAL) && (val2)) set = TRUE;
|
|
if ((val1 == LITERAL_IVAL) && (val2 == 0)) set = FALSE;
|
|
}
|
|
|
|
int c = I6TargetCode::pval_case(storage_ref);
|
|
if ((c == I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE) && (bip == STORE_BIP) && (set == TRUE)) {
|
|
WRITE("give "); Vanilla::node(gen, InterTree::second_child(storage_ref));
|
|
WRITE(" %S", I6TargetCode::inner_name(gen, InterTree::third_child(storage_ref)));
|
|
} else if ((c == I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE) && (bip == STORE_BIP) && (set == FALSE)) {
|
|
WRITE("give "); Vanilla::node(gen, InterTree::second_child(storage_ref));
|
|
WRITE(" ~%S", I6TargetCode::inner_name(gen, InterTree::third_child(storage_ref)));
|
|
} else {
|
|
WRITE("(");
|
|
switch (bip) {
|
|
case STORE_BIP: WRITE("_final_store_property"); break;
|
|
case PREINCREMENT_BIP: WRITE("_final_preinc_property"); break;
|
|
case POSTINCREMENT_BIP: WRITE("_final_postinc_property"); break;
|
|
case PREDECREMENT_BIP: WRITE("_final_predec_property"); break;
|
|
case POSTDECREMENT_BIP: WRITE("_final_postdec_property"); break;
|
|
case SETBIT_BIP: WRITE("_final_setbit_property"); break;
|
|
case CLEARBIT_BIP: WRITE("_final_clearbit_property"); break;
|
|
}
|
|
WRITE("(");
|
|
Vanilla::node(gen, InterTree::first_child(storage_ref));
|
|
WRITE(",");
|
|
Vanilla::node(gen, InterTree::second_child(storage_ref));
|
|
WRITE(",");
|
|
Vanilla::node(gen, InterTree::third_child(storage_ref));
|
|
switch (bip) {
|
|
case STORE_BIP: WRITE(", "); VNODE_2C; break;
|
|
case SETBIT_BIP: WRITE(", "); VNODE_2C; break;
|
|
case CLEARBIT_BIP: WRITE(", "); VNODE_2C; break;
|
|
}
|
|
WRITE("))");
|
|
}
|
|
|
|
@ Reading property values is easier. The general case, similarly, is to call a
|
|
function for this, but an important optimisation collapses this to the use of
|
|
the |has| or |.| operators in I6 where we can prove at compile-time that the
|
|
property in question is stored as a VM-attribute (resp., a VM-property) of
|
|
what is definitely a VM-object. This optimisation results in faster code.
|
|
|
|
@<Property value access@> =
|
|
case PROPERTYEXISTS_BIP:
|
|
I6_GEN_DATA(value_ranges_needed) = TRUE;
|
|
I6_GEN_DATA(value_property_holders_needed) = TRUE;
|
|
WRITE("(_final_propertyexists("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
|
|
VNODE_3C; WRITE("))"); break;
|
|
case PROPERTYARRAY_BIP: WRITE("(_final_propertyarray("); VNODE_1C; WRITE(", ");
|
|
VNODE_2C; WRITE(", "); VNODE_3C; WRITE("))"); break;
|
|
case PROPERTYLENGTH_BIP: WRITE("(_final_propertylength("); VNODE_1C; WRITE(", ");
|
|
VNODE_2C; WRITE(", "); VNODE_3C; WRITE("))"); break;
|
|
case PROPERTYVALUE_BIP: {
|
|
inter_tree_node *KP = InterTree::first_child(P);
|
|
inter_tree_node *OP = InterTree::second_child(P);
|
|
inter_tree_node *PP = InterTree::third_child(P);
|
|
switch (I6TargetCode::pval_case(P)) {
|
|
case I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE:
|
|
WRITE("("); VNODE_2C;
|
|
WRITE(" has %S", I6TargetCode::inner_name(gen, PP)); WRITE(")"); break;
|
|
case I6G_CAN_PROVE_IS_OBJ_PROPERTY:
|
|
WRITE("("); VNODE_2C;
|
|
WRITE(".%S", I6TargetCode::inner_name(gen, PP)); WRITE(")"); break;
|
|
case I6G_CANNOT_PROVE:
|
|
I6_GEN_DATA(value_property_holders_needed) = TRUE;
|
|
I6TargetCode::eval_property_list(gen, KP, OP, PP, 0); break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
@ In the most general case of |!propertyvalue|, we will end up calling the function
|
|
|_final_propertyvalue|. But we don't do so right away because, annoyingly, |!propertyvalue|
|
|
can have |!alternative| children supplied. We might find this, for example:
|
|
= (text as Inter)
|
|
inv !if
|
|
inv !propertyvalue
|
|
val K_object harmonium
|
|
inv !alternative
|
|
val K_value P_sonorous
|
|
val K_value P_muted
|
|
=
|
|
...arising from kit code such as |if (harmonium has sonorous or muted) ...|.
|
|
This only seldom arises, so perhaps we can be given for handling it less than
|
|
optimally in all cases. We turn it into:
|
|
= (text as Inform 6)
|
|
if (((or_tmp_var = harmonium) && (or_tmp_var has sonorous)) ||
|
|
(or_tmp_var has muted))
|
|
=
|
|
Note that |or_tmp_var| is used here so that the left operand, i.e., the object,
|
|
is evaluated only once -- in case there are side-effects of the evaluation.
|
|
|
|
=
|
|
void I6TargetCode::eval_property_list(code_generation *gen, inter_tree_node *K,
|
|
inter_tree_node *X, inter_tree_node *Y, int depth) {
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
if (Y->W.data[ID_IFLD] == INV_IST) {
|
|
if (Y->W.data[METHOD_INV_IFLD] == INVOKED_PRIMITIVE) {
|
|
inter_symbol *prim = Inter::Inv::invokee(Y);
|
|
inter_ti ybip = Primitives::to_bip(gen->from, prim);
|
|
if (ybip == ALTERNATIVE_BIP) {
|
|
if (depth == 0) { WRITE("((or_tmp_var = "); Vanilla::node(gen, X); WRITE(") && (("); }
|
|
I6TargetCode::eval_property_list(gen, K, NULL, InterTree::first_child(Y), depth+1);
|
|
WRITE(") || (");
|
|
I6TargetCode::eval_property_list(gen, K, NULL, InterTree::second_child(Y), depth+1);
|
|
if (depth == 0) { WRITE(")))"); }
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
switch (I6TargetCode::pval_case_inner(K, Y)) {
|
|
case I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE:
|
|
WRITE("("); if (X) Vanilla::node(gen, X); else WRITE("or_tmp_var");
|
|
WRITE(" has %S", I6TargetCode::inner_name(gen, Y)); WRITE(")"); break;
|
|
case I6G_CAN_PROVE_IS_OBJ_PROPERTY:
|
|
WRITE("("); if (X) Vanilla::node(gen, X); else WRITE("or_tmp_var");
|
|
WRITE(".%S", I6TargetCode::inner_name(gen, Y)); WRITE(")"); break;
|
|
case I6G_CANNOT_PROVE:
|
|
WRITE("_final_propertyvalue(");
|
|
Vanilla::node(gen, K);
|
|
WRITE(", ");
|
|
if (X) Vanilla::node(gen, X); else WRITE("or_tmp_var");
|
|
WRITE(", ");
|
|
Vanilla::node(gen, Y);
|
|
WRITE(")"); break;
|
|
}
|
|
}
|
|
|
|
@<VM stack access@> =
|
|
case PUSH_BIP: WRITE("@push "); VNODE_1C; break;
|
|
case PULL_BIP: WRITE("@pull "); VNODE_1C; break;
|
|
|
|
@<Control structures@> =
|
|
case BREAK_BIP: WRITE("break"); break;
|
|
case CONTINUE_BIP: WRITE("continue"); break;
|
|
case RETURN_BIP: @<Generate primitive for return@>; break;
|
|
case JUMP_BIP: WRITE("jump "); VNODE_1C; break;
|
|
case QUIT_BIP: WRITE("quit"); break;
|
|
case RESTORE_BIP: WRITE("restore "); VNODE_1C; break;
|
|
case IF_BIP: @<Generate primitive for if@>; break;
|
|
case IFDEBUG_BIP: @<Generate primitive for ifdebug@>; break;
|
|
case IFSTRICT_BIP: @<Generate primitive for ifstrict@>; break;
|
|
case IFELSE_BIP: @<Generate primitive for ifelse@>; break;
|
|
case WHILE_BIP: @<Generate primitive for while@>; break;
|
|
case DO_BIP: @<Generate primitive for do@>; break;
|
|
case FOR_BIP: @<Generate primitive for for@>; break;
|
|
case OBJECTLOOP_BIP: @<Generate primitive for objectloop@>; break;
|
|
case OBJECTLOOPX_BIP: @<Generate primitive for objectloopx@>; break;
|
|
case LOOP_BIP: @<Generate primitive for loop@>; break;
|
|
case SWITCH_BIP: @<Generate primitive for switch@>; break;
|
|
case CASE_BIP: @<Generate primitive for case@>; break;
|
|
case ALTERNATIVECASE_BIP: VNODE_1C; WRITE(", "); VNODE_2C; break;
|
|
case DEFAULT_BIP: @<Generate primitive for default@>; break;
|
|
|
|
@<Generate primitive for return@> =
|
|
int rboolean = NOT_APPLICABLE;
|
|
inter_tree_node *V = InterTree::first_child(P);
|
|
if (V->W.data[ID_IFLD] == VAL_IST) {
|
|
inter_ti val1 = V->W.data[VAL1_VAL_IFLD];
|
|
inter_ti val2 = V->W.data[VAL2_VAL_IFLD];
|
|
if (val1 == LITERAL_IVAL) {
|
|
if (val2 == 0) rboolean = FALSE;
|
|
if (val2 == 1) rboolean = TRUE;
|
|
}
|
|
}
|
|
switch (rboolean) {
|
|
case FALSE: WRITE("rfalse"); break;
|
|
case TRUE: WRITE("rtrue"); break;
|
|
case NOT_APPLICABLE: WRITE("return "); Vanilla::node(gen, V); break;
|
|
}
|
|
|
|
@<Generate primitive for if@> =
|
|
WRITE("if ("); VNODE_1C; WRITE(") {\n"); INDENT; VNODE_2C;
|
|
OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for ifdebug@> =
|
|
WRITE("#ifdef DEBUG;\n"); INDENT; VNODE_1C; OUTDENT; WRITE("#endif;\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for ifstrict@> =
|
|
WRITE("#ifdef STRICT_MODE;\n"); INDENT; VNODE_1C; OUTDENT; WRITE("#endif;\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for ifelse@> =
|
|
WRITE("if ("); VNODE_1C; WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT;
|
|
WRITE("} else {\n"); INDENT; VNODE_3C; OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for while@> =
|
|
WRITE("while ("); VNODE_1C; WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for do@> =
|
|
WRITE("do {"); VNODE_2C; WRITE("} until (\n"); INDENT; VNODE_1C; OUTDENT; WRITE(")\n");
|
|
|
|
@<Generate primitive for for@> =
|
|
WRITE("for (");
|
|
inter_tree_node *INIT = InterTree::first_child(P);
|
|
if (!((INIT->W.data[ID_IFLD] == VAL_IST) &&
|
|
(INIT->W.data[VAL1_VAL_IFLD] == LITERAL_IVAL) &&
|
|
(INIT->W.data[VAL2_VAL_IFLD] == 1))) VNODE_1C;
|
|
WRITE(":"); VNODE_2C;
|
|
WRITE(":");
|
|
inter_tree_node *U = InterTree::third_child(P);
|
|
if (U->W.data[ID_IFLD] != VAL_IST)
|
|
Vanilla::node(gen, U);
|
|
WRITE(") {\n"); INDENT; VNODE_4C;
|
|
OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for objectloop@> =
|
|
int in_flag = FALSE;
|
|
inter_tree_node *U = InterTree::third_child(P);
|
|
if ((U->W.data[ID_IFLD] == INV_IST) && (U->W.data[METHOD_INV_IFLD] == INVOKED_PRIMITIVE)) {
|
|
inter_symbol *prim = Inter::Inv::invokee(U);
|
|
if ((prim) && (Primitives::to_bip(I, prim) == IN_BIP)) in_flag = TRUE;
|
|
}
|
|
|
|
WRITE("objectloop ");
|
|
if (in_flag == FALSE) {
|
|
WRITE("("); VNODE_1C; WRITE(" ofclass "); VNODE_2C;
|
|
WRITE(" && ");
|
|
} VNODE_3C;
|
|
if (in_flag == FALSE) {
|
|
WRITE(")");
|
|
}
|
|
WRITE(" {\n"); INDENT; VNODE_4C;
|
|
OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for objectloopx@> =
|
|
WRITE("objectloop ("); VNODE_1C; WRITE(" ofclass "); VNODE_2C;
|
|
WRITE(") {\n"); INDENT; VNODE_3C; OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for loop@> =
|
|
WRITE("{\n"); INDENT; VNODE_1C; OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for switch@> =
|
|
WRITE("switch ("); VNODE_1C;
|
|
WRITE(") {\n"); INDENT; VNODE_2C; OUTDENT; WRITE("}\n");
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for case@> =
|
|
VNODE_1C; WRITE(":\n"); INDENT; VNODE_2C; WRITE(";\n"); OUTDENT;
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@<Generate primitive for default@> =
|
|
WRITE("default:\n"); INDENT; VNODE_1C; WRITE(";\n"); OUTDENT;
|
|
suppress_terminal_semicolon = TRUE;
|
|
|
|
@ In Inform 6, as in C, a function which returns a value can be called in a void
|
|
context (in which case its return value is thrown away); the syntax for calling
|
|
a void function is identical to that for calling a value-returning function, so
|
|
we can treat |INDIRECT0V_BIP| as the same as |INDIRECT0_BIP|, and so on.
|
|
|
|
@<Indirect function calls@> =
|
|
case INDIRECT0_BIP: case INDIRECT0V_BIP:
|
|
WRITE("("); VNODE_1C; WRITE(")()"); break;
|
|
case INDIRECT1_BIP: case INDIRECT1V_BIP:
|
|
WRITE("("); VNODE_1C; WRITE(")("); VNODE_2C; WRITE(")"); break;
|
|
case INDIRECT2_BIP: case INDIRECT2V_BIP:
|
|
WRITE("("); VNODE_1C; WRITE(")("); VNODE_2C; WRITE(","); VNODE_3C; WRITE(")");
|
|
break;
|
|
case INDIRECT3_BIP: case INDIRECT3V_BIP:
|
|
WRITE("("); VNODE_1C; WRITE(")("); VNODE_2C; WRITE(","); VNODE_3C; WRITE(",");
|
|
VNODE_4C; WRITE(")"); break;
|
|
case INDIRECT4_BIP: case INDIRECT4V_BIP:
|
|
WRITE("("); VNODE_1C; WRITE(")("); VNODE_2C; WRITE(","); VNODE_3C; WRITE(",");
|
|
VNODE_4C; WRITE(","); VNODE_5C; WRITE(")"); break;
|
|
case INDIRECT5_BIP: case INDIRECT5V_BIP:
|
|
WRITE("("); VNODE_1C; WRITE(")("); VNODE_2C; WRITE(","); VNODE_3C; WRITE(",");
|
|
VNODE_4C; WRITE(","); VNODE_5C; WRITE(","); VNODE_6C; WRITE(")"); break;
|
|
case EXTERNALCALL_BIP: internal_error("external calls impossible in Inform 6"); break;
|
|
|
|
@ Message calls are handled with functions (see below) in case the user is trying
|
|
to send a message to a property stored in an attribute, or something like that.
|
|
|
|
@<Method calls@> =
|
|
case MESSAGE0_BIP: WRITE("_final_message0("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(")"); break;
|
|
case MESSAGE1_BIP: WRITE("_final_message1("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
|
|
VNODE_3C; WRITE(")"); break;
|
|
case MESSAGE2_BIP: WRITE("_final_message2("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
|
|
VNODE_3C; WRITE(","); VNODE_4C; WRITE(")"); break;
|
|
case MESSAGE3_BIP: WRITE("_final_message3("); VNODE_1C; WRITE(", "); VNODE_2C; WRITE(", ");
|
|
VNODE_3C; WRITE(","); VNODE_4C; WRITE(","); VNODE_5C; WRITE(")"); break;
|
|
|
|
@ Note that the only styles permitted are those from the original Z-machine, which
|
|
is about the level of technology of a 1970s teletype. The |!style| number must be
|
|
a constant 1, 2 or 3, or else plain roman is all you get.
|
|
|
|
@<Textual output@> =
|
|
case PRINT_BIP: WRITE("print "); CodeGen::lt_mode(gen, PRINTING_LTM);
|
|
VNODE_1C; CodeGen::lt_mode(gen, REGULAR_LTM); break;
|
|
case PRINTCHAR_BIP: WRITE("print (char) "); VNODE_1C; break;
|
|
case PRINTNL_BIP: WRITE("new_line"); break;
|
|
case PRINTOBJ_BIP: WRITE("print (object) "); VNODE_1C; break;
|
|
case PRINTNUMBER_BIP: WRITE("print "); VNODE_1C; break;
|
|
case PRINTDWORD_BIP: WRITE("print (address) "); VNODE_1C; break;
|
|
case PRINTSTRING_BIP: WRITE("print (string) "); VNODE_1C; break;
|
|
case BOX_BIP: WRITE("box "); CodeGen::lt_mode(gen, BOX_LTM);
|
|
VNODE_1C; CodeGen::lt_mode(gen, REGULAR_LTM); break;
|
|
case SPACES_BIP: WRITE("spaces "); VNODE_1C; break;
|
|
case FONT_BIP:
|
|
WRITE("if ("); VNODE_1C; WRITE(") { font on; } else { font off; }");
|
|
suppress_terminal_semicolon = TRUE;
|
|
break;
|
|
case STYLE_BIP: {
|
|
inter_tree_node *N = InterTree::first_child(P);
|
|
if ((N->W.data[ID_IFLD] == CONSTANT_IST) &&
|
|
(N->W.data[FORMAT_CONST_IFLD] == CONSTANT_DIRECT)) {
|
|
inter_ti val2 = N->W.data[DATA_CONST_IFLD + 1];
|
|
switch (val2) {
|
|
case 1: WRITE("style bold"); break;
|
|
case 2: WRITE("style underline"); break;
|
|
case 3: WRITE("style reverse"); break;
|
|
default: WRITE("style roman");
|
|
}
|
|
} else {
|
|
WRITE("style roman");
|
|
}
|
|
break;
|
|
}
|
|
|
|
@<The VM object tree@> =
|
|
case MOVE_BIP: WRITE("move "); VNODE_1C; WRITE(" to "); VNODE_2C; break;
|
|
case REMOVE_BIP: WRITE("remove "); VNODE_1C; break;
|
|
|
|
@h Support code for property accesses.
|
|
In the following, |prop_node| is a |VAL_IST| identifying the property being
|
|
accessed: we return the "inner name" as text, if we can find one. This will only
|
|
happen if the node evaluates to a named symbol which is the name of a property.
|
|
See //Vanilla Objects// for more on inner names.
|
|
|
|
=
|
|
text_stream *I6TargetCode::inner_name(code_generation *gen, inter_tree_node *prop_node) {
|
|
inter_symbol *prop_symbol = NULL;
|
|
if (prop_node->W.data[ID_IFLD] == VAL_IST) {
|
|
inter_ti val1 = prop_node->W.data[VAL1_VAL_IFLD];
|
|
inter_ti val2 = prop_node->W.data[VAL2_VAL_IFLD];
|
|
if (Inter::Symbols::is_stored_in_data(val1, val2))
|
|
prop_symbol =
|
|
InterSymbolsTables::symbol_from_id(Inter::Packages::scope_of(prop_node), val2);
|
|
}
|
|
if ((prop_symbol) && (Inter::Symbols::get_flag(prop_symbol, ATTRIBUTE_MARK_BIT))) {
|
|
return VanillaObjects::inner_property_name(gen, prop_symbol);
|
|
} else if ((prop_symbol) && (prop_symbol->definition->W.data[ID_IFLD] == PROPERTY_IST)) {
|
|
return VanillaObjects::inner_property_name(gen, prop_symbol);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
@ |I6TargetCode::pval_case| applies to a |!propertyvalue| invocation node. That
|
|
has three children: the kind, the object/owner, and the property itself. We
|
|
look at the node and try to see if it's one of the two easy cases which enable
|
|
more efficient code to be compiled (see above): in fact, it almost always is.
|
|
|
|
(*) We return |I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE| if the kind is definitely |OBJECT_TY|
|
|
and the property is stored in a VM-attribute;
|
|
(*) Or |I6G_CAN_PROVE_IS_OBJ_PROPERTY| if the kind is definitely |OBJECT_TY|
|
|
and the property is stored in a VM-property;
|
|
(*) Or |I6G_CANNOT_PROVE| if we don't know.
|
|
|
|
@d I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE 1
|
|
@d I6G_CAN_PROVE_IS_OBJ_PROPERTY 2
|
|
@d I6G_CANNOT_PROVE 3
|
|
|
|
=
|
|
int I6TargetCode::pval_case(inter_tree_node *P) {
|
|
while (P->W.data[ID_IFLD] == REFERENCE_IST) P = InterTree::first_child(P);
|
|
inter_tree_node *prop_node = InterTree::third_child(P);
|
|
return I6TargetCode::pval_case_inner(InterTree::first_child(P), prop_node);
|
|
}
|
|
|
|
int I6TargetCode::pval_case_inner(inter_tree_node *kind_node, inter_tree_node *prop_node) {
|
|
inter_symbol *kind_symbol = NULL;
|
|
if (kind_node->W.data[ID_IFLD] == VAL_IST) {
|
|
inter_ti val1 = kind_node->W.data[VAL1_VAL_IFLD];
|
|
inter_ti val2 = kind_node->W.data[VAL2_VAL_IFLD];
|
|
if (Inter::Symbols::is_stored_in_data(val1, val2))
|
|
kind_symbol =
|
|
InterSymbolsTables::symbol_from_id(Inter::Packages::scope_of(kind_node), val2);
|
|
}
|
|
if (Str::eq(Inter::Symbols::name(kind_symbol), I"OBJECT_TY") == FALSE)
|
|
return I6G_CANNOT_PROVE;
|
|
|
|
inter_symbol *prop_symbol = NULL;
|
|
if (prop_node->W.data[ID_IFLD] == VAL_IST) {
|
|
inter_ti val1 = prop_node->W.data[VAL1_VAL_IFLD];
|
|
inter_ti val2 = prop_node->W.data[VAL2_VAL_IFLD];
|
|
if (Inter::Symbols::is_stored_in_data(val1, val2))
|
|
prop_symbol =
|
|
InterSymbolsTables::symbol_from_id(Inter::Packages::scope_of(prop_node), val2);
|
|
}
|
|
if ((prop_symbol) && (Inter::Symbols::get_flag(prop_symbol, ATTRIBUTE_MARK_BIT))) {
|
|
return I6G_CAN_PROVE_IS_OBJ_ATTRIBUTE;
|
|
} else if ((prop_symbol) && (prop_symbol->definition->W.data[ID_IFLD] == PROPERTY_IST)) {
|
|
return I6G_CAN_PROVE_IS_OBJ_PROPERTY;
|
|
} else {
|
|
return I6G_CANNOT_PROVE;
|
|
}
|
|
}
|
|
|
|
@h The final functions.
|
|
The generator above compiled calls to a handful of functions with names in the
|
|
form |_final_*|; so these functions must clearly be supplied. It might seem that
|
|
they ought to be included in, say, BasicInformKit and not here. But:
|
|
|
|
(1) They are needed only for Inform 6 usage, whereas BasicInformKit contains
|
|
material used whatever the final code-generator;
|
|
(2) They are written in genuine Inform 6 code, not kit code, which looks like
|
|
I6 and is very similar to it but is not quite the same. In kit code, |O.P| means
|
|
"the value of the property P for the object O", but where |P| is the metadata
|
|
array for the property. In genuine Inform 6, |O.P| expects |P| to be the actual
|
|
VM-property. The following functions need the latter interpretation in order to work.
|
|
|
|
=
|
|
void I6TargetCode::end_generation(code_generator *cgt, code_generation *gen) {
|
|
segmentation_pos saved = CodeGen::select(gen, functions_I7CGS);
|
|
text_stream *OUT = CodeGen::current(gen);
|
|
@<Most general implementation of !propertyvalue@>;
|
|
@<Most general implementation of !propertyexists@>;
|
|
@<Most general implementation of !propertyarray@>;
|
|
@<Most general implementation of !propertylength@>;
|
|
@<Most general implementation of writing to a property@>;
|
|
@<Implementation of !messageX@>;
|
|
CodeGen::deselect(gen, saved);
|
|
}
|
|
|
|
@ See //Inform 6 Objects// for the runtime contents of the array of property
|
|
metadata |p|. The following is more or less a safe general-purpose wrapper for
|
|
the Inform 6 operator |.|, used as an rvalue, in cases where we cannot prove
|
|
it would be safe to use |.| directly:
|
|
|
|
@<Most general implementation of !propertyvalue@> =
|
|
WRITE("[ _final_propertyvalue K o p t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) { if (o has p) rtrue; rfalse; }\n");
|
|
WRITE(" if (o provides p) return o.p;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" rfalse;\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" t = value_property_holders-->K;\n");
|
|
WRITE(" return (t.(p-->1))-->(o+COL_HSIZE);\n");
|
|
WRITE(" }\n");
|
|
WRITE("];\n");
|
|
|
|
@ Similarly, this is a safe wrapper for |provides|:
|
|
|
|
@<Most general implementation of !propertyexists@> =
|
|
WRITE("[ _final_propertyexists K o p holder;\n");
|
|
WRITE("if (K == OBJECT_TY) {\n");
|
|
WRITE(" if ((o) && (metaclass(o) == Object)) {\n");
|
|
WRITE(" if ((p-->0 == 2) || (o provides p-->1)) {\n");
|
|
WRITE(" rtrue;\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" rfalse;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" rfalse;\n");
|
|
WRITE(" }\n");
|
|
WRITE("} else {\n");
|
|
WRITE(" if ((o >= 1) && (o <= value_ranges-->K)) {\n");
|
|
WRITE(" holder = value_property_holders-->K;\n");
|
|
WRITE(" if ((holder) && (holder provides p-->1)) {\n");
|
|
WRITE(" rtrue;\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" rfalse;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" rfalse;\n");
|
|
WRITE(" }\n");
|
|
WRITE("}\n");
|
|
WRITE("rfalse; ];\n");
|
|
|
|
@ And this for |.&|. Note that we always return 0 if the owner is not an object.
|
|
|
|
@<Most general implementation of !propertyarray@> =
|
|
WRITE("[ _final_propertyarray K o p v t;\n");
|
|
WRITE(" if (K ~= OBJECT_TY) return 0;\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) return 0;\n");
|
|
WRITE(" return o.&p;\n");
|
|
WRITE("];\n");
|
|
|
|
@ And this for |.#|. Again, we always return 0 if the owner is not an object.
|
|
|
|
@<Most general implementation of !propertylength@> =
|
|
WRITE("[ _final_propertylength K o p v t;\n");
|
|
WRITE(" if (K ~= OBJECT_TY) return 0;\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) return 0;\n");
|
|
WRITE(" return o.#p;\n");
|
|
WRITE("];\n");
|
|
|
|
@ And this is a safe way to write to or otherwise alter |O.P|, laboriously
|
|
written out as seven functions. (Speed is more important than conciseness here.)
|
|
|
|
@<Most general implementation of writing to a property@> =
|
|
WRITE("[ _final_store_property K o p v t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (v) give o p; else give o ~p;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" o.p = v;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" ((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE) = v;\n");
|
|
WRITE(" }\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_preinc_property K o p t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (o has p) { give o ~p; rfalse; } give o p; rtrue;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" return ++(o.p);\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" return ++(((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE));\n");
|
|
WRITE(" }\n");
|
|
WRITE(" return 0;\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_predec_property K o p t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (o has p) { give o ~p; rfalse; } give o p; rtrue;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" return --(o.p);\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" return --(((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE));\n");
|
|
WRITE(" }\n");
|
|
WRITE(" return 0;\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_postinc_property K o p t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (o has p) { give o ~p; rtrue; } give o p; rfalse;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" return (o.p)++;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" return (((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE))++;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" return 0;\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_postdec_property K o p t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (o has p) { give o ~p; rtrue; } give o p; rfalse;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" return (o.p)--;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" return (((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE))--;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" return 0;\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_setbit_property K o p v t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (v & 1) give o p;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" o.p = o.p | v;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" ((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE) =\n");
|
|
WRITE(" ((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE) | v;\n");
|
|
WRITE(" }\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_clearbit_property K o p v t;\n");
|
|
WRITE(" if (K == OBJECT_TY) {\n");
|
|
WRITE(" if (metaclass(o) == Object) {\n");
|
|
WRITE(" t = p-->0; p = p-->1;\n");
|
|
WRITE(" if (t == 2) {\n");
|
|
WRITE(" if (v & 1) give o ~p;\n");
|
|
WRITE(" } else if (o provides p) {\n");
|
|
WRITE(" o.p = o.p & ~v;\n");
|
|
WRITE(" }\n");
|
|
WRITE(" }\n");
|
|
WRITE(" } else {\n");
|
|
WRITE(" ((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE) =\n");
|
|
WRITE(" ((value_property_holders-->K).(p-->1))-->(o+COL_HSIZE) & ~v;\n");
|
|
WRITE(" }\n");
|
|
WRITE("];\n");
|
|
|
|
@ It's not entirely clear what the result of trying to send a message to an
|
|
either/or property ought to be: nobody should ever do that. We're going to say
|
|
that it's 0 here.
|
|
|
|
@<Implementation of !messageX@> =
|
|
WRITE("[ _final_message0 o p q x a rv;\n");
|
|
WRITE(" if (p-->0 == 2) return 0;\n");
|
|
WRITE(" q = p-->1; return o.q();\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_message1 o p v1 q x a rv;\n");
|
|
WRITE(" if (p-->0 == 2) return 0;\n");
|
|
WRITE(" q = p-->1; return o.q(v1);\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_message2 o p v1 v2 q x a rv;\n");
|
|
WRITE(" if (p-->0 == 2) return 0;\n");
|
|
WRITE(" q = p-->1; return o.q(v1, v2);\n");
|
|
WRITE("];\n");
|
|
WRITE("[ _final_message3 o p v1 v2 v3 q x a rv;\n");
|
|
WRITE(" if (p-->0 == 2) return 0;\n");
|
|
WRITE(" q = p-->1; return o.q(v1, v2, v3);\n");
|
|
WRITE("];\n");
|