1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-29 05:24:57 +03:00
inform7/inter/bytecode-module/Chapter 2/Inter Nodes.w
2022-05-01 08:47:54 -05:00

436 lines
16 KiB
OpenEdge ABL

[Inode::] Inter Nodes.
To create nodes of inter code, and manage everything about them except their
tree locations.
@h Nodes.
It's essential to be able to walk an Inter tree quickly, while movements
of nodes within the tree are relatively uncommon. So we provide every
imaginable link. Suppose the structure is:
= (text)
A
B
C
D
E
=
Then the links are:
= (text)
of | A B C D E
-------------+--------------------------------------
parent | NULL A A C A
first_child | B NULL D NULL NULL
last_child | E NULL D NULL NULL
previous | NULL NULL B NULL C
next | NULL C E NULL NULL
=
Each node also knows the tree and the package it belongs to. We really aren't
concerned about memory consumption here. The Inter trees we deal with will be
large (typically 400,000 nodes), so on a 64-bit processor we might be looking
at 250 MB of memory here, and that can probably be doubled when the warehouse
memory consumption is also considered. But this is no longer prohibitive: speed
of access matters more.
=
typedef struct inter_tree_node {
struct inter_tree *tree;
struct inter_package *package;
struct inter_tree_node *parent_itn;
struct inter_tree_node *first_child_itn;
struct inter_tree_node *last_child_itn;
struct inter_tree_node *previous_itn;
struct inter_tree_node *next_itn;
struct warehouse_floor_space W;
} inter_tree_node;
@ Do not call this directly in order to create a node.
=
inter_tree_node *Inode::new_node(inter_warehouse *warehouse, inter_tree *I,
int n, inter_error_location *eloc, inter_package *owner) {
if (warehouse == NULL) internal_error("no warehouse");
if (I == NULL) internal_error("no tree supplied");
warehouse_floor_space W = InterWarehouse::make_floor_space(warehouse, n);
inter_tree_node *itn = CREATE(inter_tree_node);
itn->tree = I;
itn->package = owner;
itn->parent_itn = NULL;
itn->first_child_itn = NULL;
itn->last_child_itn = NULL;
itn->previous_itn = NULL;
itn->next_itn = NULL;
itn->W = W;
if (eloc) Inode::attach_error_location(itn, eloc);
return itn;
}
@ Instead, call one of the following. This of course should be called once
only per tree, and is called by //InterTree::new// anyway.
The root node is meaningless, but in order that all nodes correspond to
instructions, we make it a |NOP_IST|. This is not really part of the program,
though.
=
inter_tree_node *Inode::new_root_node(inter_warehouse *warehouse, inter_tree *I) {
inter_tree_node *P = Inode::new_node(warehouse, I, 2, NULL, NULL);
P->W.instruction[ID_IFLD] = (inter_ti) NOP_IST;
P->W.instruction[LEVEL_IFLD] = 0;
return P;
}
@ More generally: the content of a node is an instruction stored as bytecode
in memory. (Perhaps wordcode would be more accurate: it's a series of words.)
Words 0 and 1 have the same meaning for all instructions; everything from 2
onwards is data whose meaning differs between instructions. (Indeed, some
instructions have no data at all, and thus occupy only 2 words.)
The ID is an enumeration of |*_IST|: it marks which instruction this is.
The level is the depth of this node in the tree, where the root node is 0,
its children are level 1, their children level 2, and so on.
@d ID_IFLD 0
@d LEVEL_IFLD 1
@d DATA_IFLD 2
@ These functions should be called only by the creator functions for the
Inter instructions. Code which is generating Inter to do something should not
call those creator functions, not these.
=
inter_tree_node *Inode::new_with_0_data_fields(inter_bookmark *IBM, int S,
inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 2,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
return P;
}
inter_tree_node *Inode::new_with_1_data_field(inter_bookmark *IBM, int S,
inter_ti V, inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 3,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V;
return P;
}
inter_tree_node *Inode::new_with_2_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 4,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
return P;
}
inter_tree_node *Inode::new_with_3_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_ti V3, inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 5,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
P->W.instruction[DATA_IFLD + 2] = V3;
return P;
}
inter_tree_node *Inode::new_with_4_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_ti V3, inter_ti V4, inter_error_location *eloc,
inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 6,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
P->W.instruction[DATA_IFLD + 2] = V3;
P->W.instruction[DATA_IFLD + 3] = V4;
return P;
}
inter_tree_node *Inode::new_with_5_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_ti V3, inter_ti V4, inter_ti V5,
inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 7,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
P->W.instruction[DATA_IFLD + 2] = V3;
P->W.instruction[DATA_IFLD + 3] = V4;
P->W.instruction[DATA_IFLD + 4] = V5;
return P;
}
inter_tree_node *Inode::new_with_6_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_ti V3, inter_ti V4, inter_ti V5, inter_ti V6,
inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 8,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
P->W.instruction[DATA_IFLD + 2] = V3;
P->W.instruction[DATA_IFLD + 3] = V4;
P->W.instruction[DATA_IFLD + 4] = V5;
P->W.instruction[DATA_IFLD + 5] = V6;
return P;
}
inter_tree_node *Inode::new_with_7_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_ti V3, inter_ti V4, inter_ti V5, inter_ti V6,
inter_ti V7, inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 9,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
P->W.instruction[DATA_IFLD + 2] = V3;
P->W.instruction[DATA_IFLD + 3] = V4;
P->W.instruction[DATA_IFLD + 4] = V5;
P->W.instruction[DATA_IFLD + 5] = V6;
P->W.instruction[DATA_IFLD + 6] = V7;
return P;
}
inter_tree_node *Inode::new_with_8_data_fields(inter_bookmark *IBM, int S,
inter_ti V1, inter_ti V2, inter_ti V3, inter_ti V4, inter_ti V5, inter_ti V6,
inter_ti V7, inter_ti V8, inter_error_location *eloc, inter_ti level) {
inter_tree *I = InterBookmark::tree(IBM);
inter_tree_node *P = Inode::new_node(InterTree::warehouse(I), I, 10,
eloc, InterBookmark::package(IBM));
P->W.instruction[ID_IFLD] = (inter_ti) S;
P->W.instruction[LEVEL_IFLD] = level;
P->W.instruction[DATA_IFLD] = V1;
P->W.instruction[DATA_IFLD + 1] = V2;
P->W.instruction[DATA_IFLD + 2] = V3;
P->W.instruction[DATA_IFLD + 3] = V4;
P->W.instruction[DATA_IFLD + 4] = V5;
P->W.instruction[DATA_IFLD + 5] = V6;
P->W.instruction[DATA_IFLD + 6] = V7;
P->W.instruction[DATA_IFLD + 7] = V8;
return P;
}
@h The package and tree of a node.
=
inter_package *Inode::get_package(inter_tree_node *F) {
if (F) return F->package;
return NULL;
}
inter_tree *Inode::tree(inter_tree_node *F) {
if (F) return F->tree;
return NULL;
}
inter_symbols_table *Inode::globals(inter_tree_node *F) {
if (F) return InterTree::global_scope(Inode::tree(F));
return NULL;
}
inter_warehouse *Inode::warehouse(inter_tree_node *F) {
if (F == NULL) return NULL;
return F->W.in_room->owning_warehouse;
}
@h Bytecode storage.
Each node represents one instruction, which is encoded with a contiguous
block of bytecode. That bytecode is not stored in the //inter_tree_node//
structure, but in warehouse memory.
= (text)
......+------+-----------------+----+-------+-------------+........
| Skip | Origin/Verified | ID | Level | Data |
......+------+-----------------+----+-------+-------------+........
<----------------------> <------------------------>
Preframe Frame
=
This stretch of memory is divided into a "preframe" and a "frame": the frame
holds the words of data described above, with position 0 being the ID, and
so on. Unlike the frame, the preframe data is not part of the instruction, and
is not saved out when Inter is stored in a binary file.
The frame is of variable size, depending on the instruction (and in
some cases its particular content: a constant definition for a list can be
almost any length). But the preframe is of fixed size:
@d PREFRAME_SIZE 2
@d PREFRAME_SKIP_AMOUNT 0
@d PREFRAME_ORIGIN_AND_VFLAG 1
@ |PREFRAME_SKIP_AMOUNT| is the offset (in words) to the next instruction.
Since the preframe has fixed length, this is both the offset from one preframe
to the next and also from one frame to the next.
@ |PREFRAME_ORIGIN_AND_VFLAG| contains two unrelated pieces of data. One is bit 31,
which is set if the instruction in the frame has been verified, and is otherwise
clear.
The other, in bits 0-30, allows the origin of the instruction, in source code,
to be preserved: for example, to show that this came from line 14 of a file
called |whatever.intert|. It is 0 if no origin is recorded; it is used only
for better reporting of any errors which arise. For how the location is
actually encoded in the word, see //The Warehouse//.
=
inter_error_location *Inode::get_error_location(inter_tree_node *F) {
if (F == NULL) return NULL;
inter_ti L = (Inode::get_preframe(F, PREFRAME_ORIGIN_AND_VFLAG)) & 0x7fffffff;
return InterTree::origin_word_to_eloc(Inode::tree(F), L);
}
void Inode::attach_error_location(inter_tree_node *F, inter_error_location *eloc) {
inter_ti val = InterTree::eloc_to_origin_word(Inode::tree(F), eloc);
if ((Inode::get_preframe(F, PREFRAME_ORIGIN_AND_VFLAG)) & 0x80000000)
val |= 0x80000000;
Inode::set_preframe(F, PREFRAME_ORIGIN_AND_VFLAG, val);
}
@ Note that the vflag can never be cleared, once set.
=
int Inode::get_vflag(inter_tree_node *F) {
return ((Inode::get_preframe(F, PREFRAME_ORIGIN_AND_VFLAG)) & 0x80000000)?TRUE:FALSE;
}
void Inode::set_vflag(inter_tree_node *F) {
Inode::set_preframe(F, PREFRAME_ORIGIN_AND_VFLAG,
(Inode::get_preframe(F, PREFRAME_ORIGIN_AND_VFLAG)) | 0x80000000);
}
@ The following gets and sets from the preframe:
=
inter_ti Inode::get_preframe(inter_tree_node *F, int at) {
if (F == NULL) return 0;
return F->W.in_room->bytecode[F->W.index + at];
}
void Inode::set_preframe(inter_tree_node *F, int at, inter_ti V) {
if (F) F->W.in_room->bytecode[F->W.index + at] = V;
}
@ As noted above, the size of the frame varies from instruction to instruction.
For most instructions, it's determined as soon as the inode is created -- for
example, a |PROPERTYVALUE_IST| is always of a fixed length, and it's created
already being that length, so that it doesn't need to be extended.
But just a few instructions are of variable length depending on what they are
doing -- |CONSTANT_IST|, for example. Those are created at their minimum
length and then extended as needed.
Note that |by| is unsigned, so cannot be negative: the bytecode can extend
but not contract.
All of this extension happens only during the process of creating the
instruction: once that's done, it never changes in length again. Because of
this, we can only build one instruction at a time.
Note that this function always succeeds, because an internal error is thrown
if the system is out of memory.
=
void Inode::extend_instruction_by(inter_tree_node *F, inter_ti by) {
if (by > 0) F->W = InterWarehouse::enlarge_floor_space(F->W, by);
}
@ Every instruction has its identity and a level:
=
int Inode::is(inter_tree_node *P, inter_ti construct_ID) {
if (P == NULL) return FALSE;
if (P->W.instruction[ID_IFLD] == construct_ID) return TRUE;
return FALSE;
}
int Inode::isnt(inter_tree_node *P, inter_ti construct_ID) {
if (P == NULL) return TRUE;
if (P->W.instruction[ID_IFLD] == construct_ID) return FALSE;
return TRUE;
}
inter_ti Inode::get_construct_ID(inter_tree_node *P) {
if (P == NULL) return 0;
return P->W.instruction[ID_IFLD];
}
int Inode::get_level(inter_tree_node *P) {
if (P == NULL) return 0;
return (int) P->W.instruction[LEVEL_IFLD];
}
void Inode::set_level(inter_tree_node *P, int L) {
if (P == NULL) internal_error("no node");
P->W.instruction[LEVEL_IFLD] = (inter_ti) L;
}
@h Interpreting values stored in bytecode.
The data stored in the fields above may represent strings, symbols and the like,
but it's just stored in what amounts to an array of unsigned integers: what is
stored is just an ID representing one of those things. The conversion from an ID
to the actual resource it represents depends on:
(a) What it is meant to be (symbols table, text, package, ...) -- there is no
indication in the bytecode as to which that is, so we need to know what we're
looking for, e.g. that what is in position 3 is a text;
(b) Which node the value came from -- because these depend on the warehouse
where data is stored (and some nodes are in different warehouses than others),
and sometimes also on the local symbols table (which depends on the package
we are in, which in turn depends on the node).
=
inter_symbols_table *Inode::ID_to_symbols_table(inter_tree_node *F, inter_ti ID) {
return InterWarehouse::get_symbols_table(Inode::warehouse(F), ID);
}
text_stream *Inode::ID_to_text(inter_tree_node *F, inter_ti ID) {
return InterWarehouse::get_text(Inode::warehouse(F), ID);
}
inter_package *Inode::ID_to_package(inter_tree_node *F, inter_ti ID) {
if (ID == 0) return NULL;
return InterWarehouse::get_package(Inode::warehouse(F), ID);
}
inter_node_list *Inode::ID_to_frame_list(inter_tree_node *F, inter_ti N) {
return InterWarehouse::get_node_list(Inode::warehouse(F), N);
}
@h Errors.
Suppose we want to generate an error message for malformed Inter code, and
to identify it as occurring at a particular node. We can get one thus:
=
inter_error_message *Inode::error(inter_tree_node *F, text_stream *err, text_stream *quote) {
inter_error_message *iem = CREATE(inter_error_message);
inter_error_location *eloc = Inode::get_error_location(F);
if (eloc)
iem->error_at = *eloc;
else
iem->error_at = InterErrors::file_location(NULL, NULL);
iem->error_body = err;
iem->error_quote = quote;
return iem;
}