To compile the tables submodule for a compilation unit, which contains _table packages containing _table_column_usage subpackages.
- §1. Compilation data for tables
- §2. Compilation of tables
- §4. Compilation data for table column usages
- §6. Kinds as tables
§1. Compilation data for tables. Each table object contains this data:
typedef struct table_compilation_data { struct package_request *table_package; struct inter_name *table_identifier; struct wording name_for_metadata; struct parse_node *where_created; int translated; struct text_stream *translated_name; struct inter_name *translated_iname; } table_compilation_data; table_compilation_data RTTables::new_table(parse_node *PN, table *t, wording W) { table_compilation_data tcd; tcd.table_package = NULL; tcd.table_identifier = NULL; tcd.name_for_metadata = W; tcd.where_created = PN; tcd.translated = FALSE; tcd.translated_name = NULL; tcd.translated_iname = NULL; return tcd; } package_request *RTTables::package(table *t) { if (t->compilation_data.table_package == NULL) t->compilation_data.table_package = Hierarchy::local_package_to(TABLES_HAP, t->compilation_data.where_created); return t->compilation_data.table_package; } inter_name *RTTables::identifier(table *t) { if (t->compilation_data.table_identifier == NULL) t->compilation_data.table_identifier = Hierarchy::make_iname_in(TABLE_DATA_HL, RTTables::package(t)); return t->compilation_data.table_identifier; } inter_name *RTTables::id_translated(table *t) { if (Str::len(t->compilation_data.translated_name) == 0) return NULL; if (t->compilation_data.translated_iname == NULL) { t->compilation_data.translated_iname = InterNames::explicitly_named( t->compilation_data.translated_name, RTTables::package(t)); Hierarchy::make_available(t->compilation_data.translated_iname); } return t->compilation_data.translated_iname; } void RTTables::translate(table *t, wording W) { if (t->compilation_data.translated) { StandardProblems::sentence_problem(Task::syntax_tree(), _p_(PM_TranslatesTableAlready), "this table has already been translated", "so there must be some duplication somewhere."); return; } t->compilation_data.translated = TRUE; t->compilation_data.translated_name = Str::new(); WRITE_TO(t->compilation_data.translated_name, "%N", Wordings::first_wn(W)); }
- The structure table_compilation_data is accessed in 3/uo, 5/act, 5/mlt, 5/tc, 5/dbi, 5/dli, 5/dci, 5/rlb, 5/prp, 5/lp, 5/act2, 5/nap and here.
void RTTables::compile(void) { table *t; LOOP_OVER(t, table) if (t->amendment_of == FALSE) { text_stream *desc = Str::new(); WRITE_TO(desc, "table '%W'", t->compilation_data.name_for_metadata); Sequence::queue(&RTTables::compilation_agent, STORE_POINTER_table(t), desc); } } void RTTables::compilation_agent(compilation_subtask *ct) { table *t = RETRIEVE_POINTER_table(ct->data); current_sentence = t->table_created_at->source_table; int blanks_array_hwm = 0; the high water mark of storage used in the blanks array Compile the run-time storage for the table2.1; Compile metadata for the table2.3; Compile the blanks bitmap table2.2; Hierarchy::apply_metadata_from_iname(RTTables::package(t), TABLE_VALUE_MD_HL, RTTables::identifier(t)); if (t == TheScore::ranking_table()) Hierarchy::apply_metadata_from_number(RTTables::package(t), RANKING_TABLE_MD_HL, 1); inter_name *translated = RTTables::id_translated(t); if (translated) Emit::iname_constant(translated, K_value, RTTables::identifier(t)); }
§2.1. Compile the run-time storage for the table2.1 =
int words_used = 0; current_sentence = t->table_created_at->source_table; Compile the outer table array2.1.1; t->approximate_array_space_needed = words_used;
- This code is used in §2.
§2.1.1. At run time, the data in T is essentially stored as a table of column tables, one for each column. A column table begins with a word identifying the table column number (so that two columns both called "price" in different tables will have the same identifying value heading their column tables), together with special bits set to indicate that exotic types of data are stored inside. Thus if T has C columns, the column tables are found at T-->1, T-->2, ..., T-->C.
Compile the outer table array2.1.1 =
for (int j=0; j<t->no_columns; j++) { Compile the inner table array for column j2.1.1.1; } packaging_state save = EmitArrays::begin_bounded(RTTables::identifier(t), K_value); for (int j=0; j<t->no_columns; j++) { EmitArrays::iname_entry(RTTables::tcu_iname(&(t->columns[j]))); } EmitArrays::end(save); words_used += t->no_columns + 1;
- This code is used in §2.1.
§2.1.1.1. Each column table C has its identifying number and bitmap combined in C-->1 (the ID occupies the lower bits), a pointer to its blanks storage in C-->2, and its actual data in C-->3, C-->4, ..., C-->(R+2), where R is the number of rows.
The contents of a cell are not just its value but also an indication of whether or not it is formally blank, which often makes it impossible to store in a single virtual machine word. In those situations we also store a single bit in the "blanks array" which records whether the cell is blank or not.
Compile the inner table array for column j2.1.1.1 =
packaging_state save = EmitArrays::begin_bounded(RTTables::tcu_iname(&(t->columns[j])), K_value); table_column *tc = t->columns[j].column_identity; LOGIF(TABLES, "Compiling column: $C\n", tc); kind *K = Tables::Columns::get_kind(tc); int bits = 0; bitmap of some properties of the column Write the bitmap and blank-offset words2.1.1.1.3; int e = 0; which bit we're up to within the current byte of the blanks array for (parse_node *cell = t->columns[j].entries->down; cell; cell = cell->next) { Write a cell value for the initial contents of this cell2.1.1.1.4; Allocate one bit in the blanks array, if needed2.1.1.1.1; words_used++; } int blank_count = t->blank_rows; if ((blank_count == 0) && (t->columns[j].entries->down == NULL)) blank_count = 1; for (int br = 0; br < blank_count; br++) { Write a cell value for a blank cell2.1.1.1.5; Allocate one bit in the blanks array, if needed2.1.1.1.1; words_used++; } Pad out the blanks array as needed2.1.1.1.2; EmitArrays::end(save); LOGIF(TABLES, "Done column: $C\n", tc);
- This code is used in §2.1.1.
§2.1.1.1.1. In this part of the code we're carefully keeping track of how much blank array storage we need (perhaps none!), but not compiling it. The sole aim is to make sure blanks_array_hwm has the correct value at the beginning of each column needing blank bits.
Allocate one bit in the blanks array, if needed2.1.1.1.1 =
if ((bits & TB_COLUMN_NOBLANKBITS) == 0) { e++; if ((e % 8) == 0) blanks_array_hwm++; }
- This code is used in §2.1.1.1 (twice).
§2.1.1.1.2. Pad out the blanks array as needed2.1.1.1.2 =
if ((bits & TB_COLUMN_NOBLANKBITS) == 0) { if ((e % 8) != 0) blanks_array_hwm++; pad out a partial byte with zero bits }
- This code is used in §2.1.1.1.
§2.1.1.1.3. The table column array begins with two words: a bitmap giving some minimal idea of what can safely be done with values, and then an address within the blanks bitmap array (if this is needed).
A weakness of this scheme occurs if column ID numbers ever grow large enough to collide with the bits used here: at present, that would need 412 different table column names, which is dangerously plausible. We should fix this.
The following flags are also defined in Tables.i6t and must agree with the values given there.
define TB_COLUMN_REAL 0x8000 define TB_COLUMN_SIGNED 0x4000 define TB_COLUMN_TOPIC 0x2000 define TB_COLUMN_DONTSORTME 0x1000 define TB_COLUMN_NOBLANKBITS 0x0800 define TB_COLUMN_CANEXCHANGE 0x0400 define TB_COLUMN_ALLOCATED 0x0200
Write the bitmap and blank-offset words2.1.1.1.3 =
if (Kinds::Behaviour::can_exchange(K)) bits += TB_COLUMN_CANEXCHANGE; if ((Kinds::Behaviour::uses_signed_comparisons(K)) || (Kinds::FloatingPoint::uses_floating_point(K))) bits += TB_COLUMN_SIGNED; if (Kinds::FloatingPoint::uses_floating_point(K)) bits += TB_COLUMN_REAL; if (Kinds::Behaviour::uses_block_values(K)) bits += TB_COLUMN_ALLOCATED; if ((K_understanding) && (Kinds::eq(K, K_understanding))) bits = TB_COLUMN_TOPIC; if (RTTables::requires_blanks_bitmap(K) == FALSE) bits += TB_COLUMN_NOBLANKBITS; if (t->preserve_row_order_at_run_time) bits += TB_COLUMN_DONTSORTME; inter_name *bits_iname = Hierarchy::make_iname_in(COLUMN_BITS_HL, RTTables::tcu_package(&(t->columns[j]))); Emit::numeric_constant(bits_iname, (inter_ti) bits); EmitArrays::iname_entry(bits_iname); inter_name *identity_iname = Hierarchy::make_iname_in(COLUMN_IDENTITY_HL, RTTables::tcu_package(&(t->columns[j]))); Emit::iname_constant(identity_iname, K_value, RTTableColumns::id_iname(tc)); if (bits & TB_COLUMN_NOBLANKBITS) { EmitArrays::null_entry(); } else { inter_name *blanks_iname = Hierarchy::make_iname_in(COLUMN_BLANKS_HL, RTTables::tcu_package(&(t->columns[j]))); Emit::numeric_constant(blanks_iname, (inter_ti) blanks_array_hwm); EmitArrays::iname_entry(blanks_iname); } words_used += 2;
- This code is used in §2.1.1.1.
§2.1.1.1.4. The cell can only contain a generic value in the case of column 1 of a table used to define new kinds; in this case it doesn't matter what we write, but nothing has the virtue of being typesafe.
Write a cell value for the initial contents of this cell2.1.1.1.4 =
current_sentence = cell; if (Annotations::read_int(cell, table_cell_unspecified_ANNOT)) { Write a cell value for a blank cell2.1.1.1.5; } else { #ifdef IF_MODULE if (bits & TB_COLUMN_TOPIC) { wording W = Node::get_text(cell); EmitArrays::generic_entry(CompileRvalues::compile_understanding(W)); } else { #endif parse_node *val = Node::get_evaluation(cell); if (Specifications::is_kind_like(val)) EmitArrays::numeric_entry(0); else if (val == NULL) internal_error("Valueless cell"); else CompileValues::to_array_entry_of_kind(val, K); #ifdef IF_MODULE } #endif } words_used++;
- This code is used in §2.1.1.1.
§2.1.1.1.5. As we've noted, our storage for a cell is both the array value we write here and also a separate bit in the blanks array. In practice, though, it's inefficient to look up two parallel structures each time we want to access the cell. So a blank cell is represented as both the value TABLE_NOVALUE, chosen so that it is very unlikely ever to occur as a genuine table value (currently it's the picturesque hexadecimal value 0xDEADCE11), and also as a blank bit set in the blanks array. This makes a negative check — that something is not blank — very quick, since only in the very rare case when the value does coincide with TABLE_NOVALUE do we need to check the blanks array. A positive check necessarily takes longer, but this cannot be helped.
With some kinds it is possible to prove that TABLE_NOVALUE cannot be a legal value, and then space in the blanks array isn't needed: this is when the TB_COLUMN_NOBLANKBITS flag is set. These kinds include all enumerated kinds and also object numbers. (The latter are enumerated in the Z-machine but not in Glulx: however, TABLE_NOVALUE is so large a number that it can never be an object reference value in any story file smaller than 3.5 GB, and in practice I6 and I7 are not capable of generating story files above 2 GB in any case.)
Write a cell value for a blank cell2.1.1.1.5 =
if (t->fill_in_blanks == FALSE) EmitArrays::iname_entry(Hierarchy::find(TABLE_NOVALUE_HL)); else DefaultValues::array_entry(K, EMPTY_WORDING, "table entry");
- This code is used in §2.1.1.1, §2.1.1.1.4.
§2.2. Compile the blanks bitmap table2.2 =
for (int j=0; j<t->no_columns; j++) { table_column *tc = t->columns[j].column_identity; if (RTTables::requires_blanks_bitmap(Tables::Columns::get_kind(tc)) == FALSE) continue; inter_name *iname = Hierarchy::make_iname_in(COLUMN_BLANK_DATA_HL, RTTables::tcu_package(&(t->columns[j]))); packaging_state save = EmitArrays::begin_byte(iname, K_number); int current_bit = 1, byte_so_far = 0; Compile blank bits for entries from the source text2.2.1; Compile blank bits for additional blank rows2.2.2; if (current_bit != 1) Ship the current byte of the blanks table2.2.3; EmitArrays::end(save); }
- This code is used in §2.
§2.2.1. Compile blank bits for entries from the source text2.2.1 =
parse_node *cell; for (cell = t->columns[j].entries->down; cell; cell = cell->next) { if ((Annotations::read_int(cell, table_cell_unspecified_ANNOT)) && (t->fill_in_blanks == FALSE)) byte_so_far += current_bit; current_bit = current_bit*2; if (current_bit == 256) Ship the current byte of the blanks table2.2.3; }
- This code is used in §2.2.
§2.2.2. Compile blank bits for additional blank rows2.2.2 =
int k; for (k = 0; k < t->blank_rows; k++) { byte_so_far += current_bit; current_bit = current_bit*2; if (current_bit == 256) Ship the current byte of the blanks table2.2.3; }
- This code is used in §2.2.
§2.2.3. Ship the current byte of the blanks table2.2.3 =
EmitArrays::numeric_entry((inter_ti) byte_so_far); byte_so_far = 0; current_bit = 1;
§2.3. Compile metadata for the table2.3 =
Hierarchy::apply_metadata_from_wording(RTTables::package(t), TABLE_NAME_MD_HL, t->compilation_data.name_for_metadata); inter_name *iname = Hierarchy::make_iname_in(TABLE_ID_HL, RTTables::package(t)); Emit::numeric_constant(iname, 0); TEMPORARY_TEXT(S) WRITE_TO(S, "%+W", Node::get_text(t->headline_fragment)); Hierarchy::apply_metadata(RTTables::package(t), TABLE_PNAME_MD_HL, S); DISCARD_TEXT(S) for (table_contribution *tc = t->table_created_at; tc; tc = tc->next) { package_request *R = Hierarchy::package_within(TABLE_CONTRIBUTION_HAP, RTTables::package(t)); Hierarchy::apply_metadata_from_number(R, TABLE_CONTRIBUTION_AT_MD_HL, (inter_ti) Wordings::first_wn(Node::get_text(tc->source_table))); } Hierarchy::apply_metadata_from_number(RTTables::package(t), TABLE_ROWS_MD_HL, (inter_ti) Tables::get_no_rows(t)); Hierarchy::apply_metadata_from_number(RTTables::package(t), TABLE_BLANK_ROWS_MD_HL, (inter_ti) t->blank_rows); if (Wordings::nonempty(t->blank_rows_for_each_text)) Hierarchy::apply_metadata_from_raw_wording(RTTables::package(t), TABLE_BLANK_ROWS_FOR_MD_HL, t->blank_rows_for_each_text); if (t->first_column_by_definition) { Hierarchy::apply_metadata_from_number(RTTables::package(t), TABLE_DEFINES_MD_HL, 1); Hierarchy::apply_metadata_from_raw_wording(RTTables::package(t), TABLE_DEFINES_TEXT_MD_HL, Node::get_text(t->where_used_to_define)); Hierarchy::apply_metadata_from_number(RTTables::package(t), TABLE_DEFINES_AT_MD_HL, (inter_ti) Wordings::first_wn(Node::get_text(t->where_used_to_define))); }
- This code is used in §2.
§3. The issue here is whether the value IMPROBABLE_VALUE can, despite its improbability, be valid for this kind. If we can prove that it is not, we should return FALSE; if in any doubt, we must return TRUE.
int RTTables::requires_blanks_bitmap(kind *K) { if (K == NULL) return FALSE; if (Kinds::Behaviour::is_object(K)) return FALSE; if (Kinds::Behaviour::is_an_enumeration(K)) return FALSE; return TRUE; }
§4. Compilation data for table column usages. Each table_column_usage object contains this data:
typedef struct table_column_usage_compilation_data { struct table *owning_table; struct package_request *tcu_package; struct inter_name *tcu_iname; for the array holding this at run-time } table_column_usage_compilation_data; table_column_usage_compilation_data RTTables::new_tcu_compilation_data(table *t) { table_column_usage_compilation_data tcucd; tcucd.owning_table = t; tcucd.tcu_package = NULL; tcucd.tcu_iname = NULL; return tcucd; }
- The structure table_column_usage_compilation_data is private to this section.
§5. And each gives rise to a subpackage of the package for the table it appears in:
package_request *RTTables::tcu_package(table_column_usage *tcu) { if (tcu->compilation_data.tcu_package == NULL) tcu->compilation_data.tcu_package = Hierarchy::package_within(TABLE_COLUMN_USAGES_HAP, RTTables::package(tcu->compilation_data.owning_table)); return tcu->compilation_data.tcu_package; } inter_name *RTTables::tcu_iname(table_column_usage *tcu) { if (tcu->compilation_data.tcu_iname == NULL) tcu->compilation_data.tcu_iname = Hierarchy::make_iname_in(COLUMN_DATA_HL, RTTables::tcu_package(tcu)); return tcu->compilation_data.tcu_iname; }
table *RTTables::table_defining_this(kind *K) { if (K == NULL) return NULL; return K->construct->named_values_created_with_table; } void RTTables::defines(table *t, kind *K) { if (K == NULL) internal_error("no such kind"); K->construct->named_values_created_with_table = t; }