Tables contents
The I7 Table structure is not to be confused with the I6 table form of array: it is essentially a two-dimensional array which has some metadata at the top of each column.
The run-time representation for a Table is the address T of an I6 table array: that is, T-->0 holds the number of columns (which is at most 99) and T-->i is the address of column number i. Columns are therefore numbered from 1 to T-->0, but they are also identified by an ID number of 100 or more, with each different column name having its own ID number. (This is so that multiple tables can share columns with the same name, and correlate them: it also means that NI's type-checking machinery can know the kind of value of a table entry from the name of the column alone.)
Each column C is also a table array, with C-->1 holding the unique ID number for the column's name, C-->2 holding the blank entry flags offset and C-->3 up to C--> holding the entries.
C-->1 also contains seven upper bit flags. These are also defined in "Tables.w" in the NI source, and the values must agree.
29Constant TB_COLUMN_REAL $8000;
30Constant TB_COLUMN_SIGNED $4000;
31Constant TB_COLUMN_TOPIC $2000;
32Constant TB_COLUMN_DONTSORTME $1000;
33Constant TB_COLUMN_NOBLANKBITS $0800;
34Constant TB_COLUMN_CANEXCHANGE $0400;
35Constant TB_COLUMN_ALLOCATED $0200;
36Constant TB_COLUMN_NUMBER $01ff;
37
38Constant COL_HSIZE 2;
Find Column.
Columns can be referenced either by their physical column numbers – from 1 to, potentially, 99 – or else by unique ID numbers associated with column names. For instance, if a table has a column called "liquid capacity", then all references to its "liquid capacity entry" are via the ID number associated with this column name, which will be ≥ 100 and on the other hand ≤ TB_COLUMN_NUMBER. At present, this is only 511, so there can be at most 411 different column names across all the tables present in the source text. (It is just about possible to imagine this being a problem on a very large work, so we will probably one day revise the above to make use of the larger word-size in Glulx and so raise this limit. But so far nobody has got even close to it.)
54[ TableFindCol tab col f i no_cols n;
55 no_cols = tab-->0;
56 for (i=1: i<=no_cols: i++)
57 if (col == ((tab-->i)-->1) & TB_COLUMN_NUMBER) return i;
58 if (f) { RunTimeProblem(RTP_TABLE_NOCOL, tab); return 0; }
59 return 0;
60];
Number of Rows.
The columns in a table can be assumed all to have the same height (i.e., number of rows): thus the number of rows in T can be calculated by looking at column 1, thus...
68[ TableRows tab first_col;
69 first_col = tab-->1; if (first_col == 0) return 0;
70 return (first_col-->0) - COL_HSIZE;
71];
Blanks.
Each table entry is stored in a single word in memory: indeed, column C row R is at address (T-->C)-->(R+COL_HSIZE).
But this is not sufficient storage in all cases, because each entry can be either a value or can be designated "blank". Since, for some columns at least, the possible values include every number, we find that we have to store 216+1 possibilities given only a 16-bit memory word. (Well, or 232+1 with a 32-bit word, depending on the virtual machine.) This cannot be done.
We therefore need, at least in some cases, an additional bit of storage for each table entry which indicates whether or not it is blank. If we provided such a bit for every table entry, that would be a fairly simple system to implement, but it would also be wasteful of memory, with an overhead of about 5% in practice: and memory in the virtual machine is in very short supply. The reason such a system would be wasteful is that many columns are known to hold values which are in a narrow range; for instance, a time of day cannot exceed 1440, and there will never be more than 10,000 rulebooks or scenes, and so on. For such columns it would be more efficient and indeed faster to indicate blankness by using an exceptional value in the memory cell which is such that it cannot be valid for the kind of value stored in the column. We therefore provide a "blanks bitmap" for only some columns.
This leads us to define the following dummy value, chosen so that it is both impossible for most kinds of value – which is easy to arrange – and also unlikely for even those kinds of value where it is legal. For instance, -1 would be impossible for enumerative kinds of value such as rulebooks and scenes, but it would be a poor choice for the dummy value because it occurs pretty often as an integer. Instead we use the constant IMPROBABLE_VALUE, whose value depends on the word size of the virtual machine, and which is declared in Definitions.i6t.
An entry is therefore blank if and only if either (a) its column has no blanks bitmap and the stored entry is TABLE_NOVALUE, or (b) its column does have a blanks bitmap, the blanks bit for this entry is set, and the stored entry is also TABLE_NOVALUE.
To look up the blanks bitmap is a little slower than to access the stored entry directly. Most of the time, entries accessed will be non-blank: so it is efficient to have a system where we can quickly determine this. If we look at the entry and find that it is not TABLE_NOVALUE, then we know it is not a blank. If we find that it is TABLE_NOVALUE, on the other hand, then quite often the column has no blanks bitmap and again we have a quick answer: it's blank. Only if the column also has a blanks bitmap do we need to check that we haven't got a false negative. (The more improbable TABLE_NOVALUE is as a stored value, the rarer it is that we have to check the blanks bitmap for a non-blank entry.)
124Constant TABLE_NOVALUE = IMPROBABLE_VALUE;
Masks.
The blanks bitmaps are stored as bytes; we therefore need a quick way to test or set whether bit number i of a byte is zero, where 0 ≤ i ≤ 7. I6 provides no very useful operators here, whereas memory lookup is cheap, so we use two arrays of bitmaps:
133Array CheckTableEntryIsBlank_LU
134 -> $$00000001
135 $$00000010
136 $$00000100
137 $$00001000
138 $$00010000
139 $$00100000
140 $$01000000
141 $$10000000;
142Array CheckTableEntryIsNonBlank_LU
143 -> $$11111110
144 $$11111101
145 $$11111011
146 $$11110111
147 $$11101111
148 $$11011111
149 $$10111111
150 $$01111111;
Testing Blankness.
The following routine is the one which checks that there is no false negative: it should be used when we know that the table entry is TABLE_NOVALUE and we need to check the blank bit, if there is one, to make sure the entry is indeed blank.
The second word in the column table header, C-->2, holds the address of the blanks bitmap: this in turn contains one bit for each row, starting with the least significant bit of the first byte. If the table contains a number of rows which isn't a multiple of 8, the spare bits at the end of the last byte in the blanks bitmap are wasted, but this is an acceptable overhead in practice.
166[ CheckTableEntryIsBlank tab col row i at;
167 if (col >= 100) col = TableFindCol(tab, col);
168 if (col == 0) rtrue;
169 if ((tab-->col)-->(row+COL_HSIZE) ~= TABLE_NOVALUE) {
170 print "*** CTEIB on nonblank value ", tab, " ", col, " ", row, " ***^";
171 }
172 if (((tab-->col)-->1) & TB_COLUMN_NOBLANKBITS) rtrue;
173 row--;
174 at = ((tab-->col)-->2) + (row/8);
175 if ((TB_Blanks->at) & (CheckTableEntryIsBlank_LU->(row%8))) rtrue;
176 rfalse;
177];
Force Entry Blank.
We blank a table cell by storing TABLE_NOVALUE in its entry word and also setting the relevant bit in the blanks bitmap, if there is one.
We need to be careful if the column holds a kind of value where values are pointers to blocks of allocated memory, because if so then overwriting such a value might lead to a memory leak. So in such cases we call BlkValueFree to free the memory block. (Note that each memory block is pointed to by one and only one I7 value at any given time: we are using them as values, not pointers to values. So if this reference is deleted, it's by definition the only one.) TABLE_NOVALUE is chosen such that it cannot be an address of a memory block, which is convenient here. (The value 0 means "no memory block allocated yet".)
194[ ForceTableEntryBlank tab col row i at oldv flags;
195 if (col >= 100) col = TableFindCol(tab, col);
196 if (col == 0) rtrue;
197 flags = (tab-->col)-->1;
198 oldv = (tab-->col)-->(row+COL_HSIZE);
199 if ((flags & TB_COLUMN_ALLOCATED) && (oldv ~= 0 or TABLE_NOVALUE))
200 BlkValueFree(oldv);
201 (tab-->col)-->(row+COL_HSIZE) = TABLE_NOVALUE;
202 if (flags & TB_COLUMN_NOBLANKBITS) return;
203 row--;
204 at = ((tab-->col)-->2) + (row/8);
205 (TB_Blanks->at) = (TB_Blanks->at) | (CheckTableEntryIsBlank_LU->(row%8));
206];
Force Entry Non-Blank.
To unblank a cell, we need to clear the relevant bit in the bitmap. We then go on to write a new value in to the entry – thus overwriting the TABLE_NOVALUE value – but that isn't done here; the expectation is that whoever calls this routine is just about to write a new entry anyway.
The exception is again for columns holding a kind of value pointing to a memory block, where we create a suitable initialised but uninteresting memory block for the KOV in question, and set the entry to that.
219[ ForceTableEntryNonBlank tab col row i at oldv flags tc kov;
220 if (col >= 100) col=TableFindCol(tab, col);
221 if (col == 0) rtrue;
222 if (((tab-->col)-->1) & TB_COLUMN_NOBLANKBITS) return;
223 flags = (tab-->col)-->1;
224 oldv = (tab-->col)-->(row+COL_HSIZE);
225 if ((flags & TB_COLUMN_ALLOCATED) &&
226 (oldv == 0 or TABLE_NOVALUE)) {
227 kov = UNKNOWN_TY;
228 tc = ((tab-->col)-->1) & TB_COLUMN_NUMBER;
229 kov = TC_KOV(tc);
230 if (kov ~= UNKNOWN_TY) {
231 (tab-->col)-->(row+COL_HSIZE) = BlkValueCreate(kov);
232 }
233 }
234 row--;
235 at = ((tab-->col)-->2) + (row/8);
236 (TB_Blanks->at) = (TB_Blanks->at) & (CheckTableEntryIsNonBlank_LU->(row%8));
237];
Swapping Blank Bits.
When sorting a table, we obviously need to swap rows from time to time; if any of its columns have blanks bitmaps, then the relevant bits in them need to be swapped to match, and the following routine performs this operation for two rows in a given column.
246[ TableSwapBlankBits tab row1 row2 col at1 at2 bit1 bit2;
247 if (col >= 100) col=TableFindCol(tab, col);
248 if (col == 0) rtrue;
249 if (((tab-->col)-->1) & TB_COLUMN_NOBLANKBITS) return;
250 row1--;
251 at1 = ((tab-->col)-->2) + (row1/8);
252 row2--;
253 at2 = ((tab-->col)-->2) + (row2/8);
254 bit1 = ((TB_Blanks->at1) & (CheckTableEntryIsBlank_LU->(row1%8)));
255 bit2 = ((TB_Blanks->at2) & (CheckTableEntryIsBlank_LU->(row2%8)));
256 if (bit1) bit1 = true;
257 if (bit2) bit2 = true;
258 if (bit1 == bit2) return;
259 if (bit1) {
260 (TB_Blanks->at1)
261 = (TB_Blanks->at1) & (CheckTableEntryIsNonBlank_LU->(row1%8));
262 (TB_Blanks->at2)
263 = (TB_Blanks->at2) | (CheckTableEntryIsBlank_LU->(row2%8));
264 } else {
265 (TB_Blanks->at1)
266 = (TB_Blanks->at1) | (CheckTableEntryIsBlank_LU->(row1%8));
267 (TB_Blanks->at2)
268 = (TB_Blanks->at2) & (CheckTableEntryIsNonBlank_LU->(row2%8));
269 }
270];
Moving Blank Bits Down.
Another common table operation is to compress it by moving all the blank rows down to the bottom, so that non-blank rows occur in a contiguous block at the top: this means table sorting can be done without having to refer continually to the blanks bitmaps. The following operation is useful for keeping the blanks bitmaps up to date when blank rows are moved down.
280[ TableMoveBlankBitsDown tab row1 row2 col at atp1 bit rx;
281 if (col >= 100) col=TableFindCol(tab, col);
282 if (col == 0) rtrue;
283 if (((tab-->col)-->1) & TB_COLUMN_NOBLANKBITS) return;
284 row1--; row2--;
285
286 at = ((tab-->col)-->2) + (row1/8);
287 bit = ((TB_Blanks->at) & (CheckTableEntryIsBlank_LU->(row1%8)));
288 if (bit) bit = true;
289
290 for (rx=row1:rx<row2:rx++) {
291 atp1 = ((tab-->col)-->2) + ((rx+1)/8);
292 at = ((tab-->col)-->2) + (rx/8);
293 if ((TB_Blanks->atp1) & (CheckTableEntryIsBlank_LU->((rx+1)%8))) {
294 (TB_Blanks->at)
295 = (TB_Blanks->at) | (CheckTableEntryIsBlank_LU->(rx%8));
296 } else {
297 (TB_Blanks->at)
298 = (TB_Blanks->at) & (CheckTableEntryIsNonBlank_LU->(rx%8));
299 }
300 }
301
302 at = ((tab-->col)-->2) + (row2/8);
303 if (bit) {
304 (TB_Blanks->at)
305 = (TB_Blanks->at) | (CheckTableEntryIsBlank_LU->(row2%8));
306 } else {
307 (TB_Blanks->at)
308 = (TB_Blanks->at) & (CheckTableEntryIsNonBlank_LU->(row2%8));
309 }
310];
Table Row Corresponding.
TableRowCorr(T, C, V) returns the first row on which value V appears in column C of table T, or prints an error if it doesn't.
ExistsTableRowCorr(T, C, V) returns the first row on which V appears in column C of table T, or 0 if V does not occur at all. If the column is a topic, then we match the entry as a snippet against the value as a general parsing routine.
322[ TableRowCorr tab col lookup_value lookup_col i j f v;
323 if (col >= 100) col=TableFindCol(tab, col, true);
324 lookup_col = tab-->col;
325 j = lookup_col-->0 - COL_HSIZE;
326 if (((tab-->col)-->1) & TB_COLUMN_ALLOCATED) f=1;
327 if (f) {
328 for (i=1:i<=j:i++) {
329 v = lookup_col-->(i+COL_HSIZE);
330 if ((v == TABLE_NOVALUE) &&
331 (CheckTableEntryIsBlank(tab,col,i))) continue;
332 if (BlkValueCompare(v, lookup_value) == 0)
333 return i;
334 }
335 } else {
336 for (i=1:i<=j:i++) {
337 if ((lookup_value == TABLE_NOVALUE) &&
338 (CheckTableEntryIsBlank(tab,col,i))) continue;
339 if (lookup_col-->(i+COL_HSIZE) == lookup_value) return i;
340 }
341 }
342 return RunTimeProblem(RTP_TABLE_NOCORR, tab);
343];
344
345[ ExistsTableRowCorr tab col entry i k v f kov;
346 if (col >= 100) col=TableFindCol(tab, col);
347 if (col == 0) rfalse;
348 f=0;
349 if (((tab-->col)-->1) & TB_COLUMN_TOPIC) f=1;
350 else if (((tab-->col)-->1) & TB_COLUMN_ALLOCATED) f=2;
351 k = TableRows(tab);
352 for (i=1:i<=k:i++) {
353 v = (tab-->col)-->(i+COL_HSIZE);
354 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,i))) continue;
355 switch (f) {
356 1: if ((v)(entry/100, entry%100) ~= GPR_FAIL) return i;
357 2: if (BlkValueCompare(v, entry) == 0) return i;
358 default: if (v == entry) return i;
359 }
360 }
361
362 return 0;
363];
Table Look Up Corresponding Row.
TableLookUpCorr(T, C1, C2, V) finds the first row on which value V appears in column C2, and returns the corresponding value in C1, or prints an error if the value V cannot be found or has no corresponding value in C1.
ExistsTableLookUpCorr(T, C1, C2, V) returns true if the operation TableLookUpCorr(T, C1, C2, V) can be done, false otherwise.
375[ TableLookUpCorr tab col1 col2 lookup_value write_flag write_value cola1 cola2 i j v f;
376 if (col1 >= 100) col1=TableFindCol(tab, col1, true);
377 if (col2 >= 100) col2=TableFindCol(tab, col2, true);
378 cola1 = tab-->col1;
379 cola2 = tab-->col2;
380 j = cola2-->0;
381 f=0;
382 if (((tab-->col2)-->1) & TB_COLUMN_ALLOCATED) f=1;
383 if (((tab-->col2)-->1) & TB_COLUMN_TOPIC) f=2;
384 for (i=1+COL_HSIZE:i<=j:i++) {
385 v = cola2-->i;
386
387 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col2,i-COL_HSIZE))) continue;
388 if (f == 1) {
389 if (BlkValueCompare(v, lookup_value) ~= 0) continue;
390 } else if (f == 2) {
391 if ((v)(lookup_value/100, lookup_value%100) == GPR_FAIL) continue;
392 } else {
393 if (v ~= lookup_value) continue;
394 }
395 if (write_flag) {
396 if (write_flag == 4) ForceTableEntryBlank(tab,col1,i-COL_HSIZE);
397 else ForceTableEntryNonBlank(tab,col1,i-COL_HSIZE);
398 switch (write_flag) {
399 1: cola1-->i = write_value;
400 2: cola1-->i = cola1-->i + write_value;
401 3: cola1-->i = cola1-->i - write_value;
402 5: return cola1-->i;
403 }
404 rfalse;
405 }
406 v = cola1-->i;
407 if ((v == TABLE_NOVALUE) &&
408 (CheckTableEntryIsBlank(tab,col1,i-COL_HSIZE))) continue;
409 return v;
410 }
411 return RunTimeProblem(RTP_TABLE_NOCORR, tab);
412];
413
414[ ExistsTableLookUpCorr tab col1 col2 lookup_value cola1 cola2 i j f;
415 if (col1 >= 100) col1=TableFindCol(tab, col1, false);
416 if (col2 >= 100) col2=TableFindCol(tab, col2, false);
417 if (col1*col2 == 0) rfalse;
418 cola1 = tab-->col1; cola2 = tab-->col2;
419 j = cola2-->0;
420 f=0;
421 if (((tab-->col2)-->1) & TB_COLUMN_ALLOCATED) f=1;
422 if (((tab-->col2)-->1) & TB_COLUMN_TOPIC) f=2;
423 for (i=1+COL_HSIZE:i<=j:i++) {
424 if ((cola1-->i == TABLE_NOVALUE) &&
425 (CheckTableEntryIsBlank(tab,col1,i-COL_HSIZE))) continue;
426 if (f == 1) {
427 if (BlkValueCompare(cola2-->i, lookup_value) ~= 0) continue;
428 } else if (f == 2) {
429 if ((cola2-->i)(lookup_value/100, lookup_value%100) == GPR_FAIL) continue;
430 } else {
431 if (cola2-->i ~= lookup_value) continue;
432 }
433 rtrue;
434 }
435 rfalse;
436];
Table Look Up Entry.
TableLookUpEntry(T, C, R) returns the value at column C, row R, printing an error if that doesn't exist.
ExistsTableLookUpEntry(T, C, R) returns true if a value exists at column C, row R, false otherwise.
446[ TableLookUpEntry tab col index write_flag write_value v;
447 if (tab == 0) return RunTimeProblem(RTP_TABLE_NOTABLE2);
448 if (col >= 100) col=TableFindCol(tab, col, true);
449 if ((index < 1) || (index > TableRows(tab))) {
450 RunTimeProblem(RTP_TABLE_NOROW, tab, index); index = 1;
451 }
452 if (write_flag) {
453 switch(write_flag) {
454 1: ForceTableEntryNonBlank(tab,col,index);
455 (tab-->col)-->(index+COL_HSIZE) = write_value;
456 2: ForceTableEntryNonBlank(tab,col,index);
457 (tab-->col)-->(index+COL_HSIZE) =
458 ((tab-->col)-->(index+COL_HSIZE)) + write_value;
459 3: ForceTableEntryNonBlank(tab,col,index);
460 (tab-->col)-->(index+COL_HSIZE) =
461 ((tab-->col)-->(index+COL_HSIZE)) - write_value;
462 4: ForceTableEntryBlank(tab,col,index);
463 5: ForceTableEntryNonBlank(tab,col,index);
464 return ((tab-->col)-->(index+COL_HSIZE));
465 }
466 rfalse;
467 }
468 v = ((tab-->col)-->(index+COL_HSIZE));
469 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,index))) {
470 RunTimeProblem(RTP_TABLE_NOENTRY, tab, col, index); rfalse;
471 }
472 return v;
473];
474
475[ ExistsTableLookUpEntry tab col index v;
476 if (col >= 100) col=TableFindCol(tab, col);
477 if (col == 0) rfalse;
478 if ((index<1) || (index > TableRows(tab))) rfalse;
479 v = ((tab-->col)-->(index+COL_HSIZE));
480 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,index)))
481 rfalse;
482 rtrue;
483];
Blank Rows.
TableRowIsBlank(T, R) returns true if row R of table T is blank. (R must be a legal row number.)
TableBlankOutRow(T, R) fills row R of table T with blanks. (R must be a legal row number.)
TableBlankRows(T) returns the number of blank rows in T.
TableFilledRows(T) returns the number of non-blank rows in T.
TableBlankRow(T) finds the first blank row in T.
499[ TableRowIsBlank tab j k;
500 for (k=1:k<=tab-->0:k++) {
501 if (((tab-->k)-->(j+COL_HSIZE)) ~= TABLE_NOVALUE) rfalse;
502 if (CheckTableEntryIsBlank(tab, k, j) == false) rfalse;
503 }
504 rtrue;
505];
506
507[ TableBlankOutRow tab row k;
508 if (tab==0) return RunTimeProblem(RTP_TABLE_NOTABLE);
509 for (k=1:k<=tab-->0:k++)
510 ForceTableEntryBlank(tab, k, row);
511];
512
513[ TableBlankOutColumn tab col n k;
514 if (tab==0) return RunTimeProblem(RTP_TABLE_NOTABLE);
515 n = TableRows(tab);
516 for (k=1:k<=n:k++)
517 ForceTableEntryBlank(tab, col, k);
518];
519
520[ TableBlankOutAll tab n k;
521 if (tab==0) return RunTimeProblem(RTP_TABLE_NOTABLE);
522 n = TableRows(tab);
523 for (k=1:k<=n:k++)
524 TableBlankOutRow(tab, k);
525];
526
527[ TableBlankRows tab i j c;
528 i = TableRows(tab);
529 for (j=1:j<=i:j++)
530 if (TableRowIsBlank(tab, j)) c++;
531
532 return c;
533];
534
535[ TableFilledRows tab;
536 return TableRows(tab) - TableBlankRows(tab);
537];
538
539[ TableBlankRow tab i j;
540 i = TableRows(tab);
541 for (j=1:j<=i:j++)
542 if (TableRowIsBlank(tab, j)) return j;
543 RunTimeProblem(RTP_TABLE_NOMOREBLANKS, tab);
544 return i;
545];
Random Row.
TableRandomRow(T) chooses a random non-blank row in T.
551[ TableRandomRow tab i j k;
552 i = TableRows(tab);
553 j = TableFilledRows(tab);
554 if (j==0) return RunTimeProblem(RTP_TABLE_NOROWS, tab);
555 if (j>1) j = random(j);
556 for (k=1:k<=i:k++) {
557 if (TableRowIsBlank(tab, k) == false) j--;
558 if (j==0) return k;
559 }
560];
Swap Rows.
TableSwapRows(T, R1, R2) exchanges rows R1 and R2.
566[ TableSwapRows tab i j k l v1 v2;
567 if (i==j) return;
568 l = tab-->0;
569 for (k=1:k<=l:k++) {
570 v1 = (tab-->k)-->(i+COL_HSIZE);
571 v2 = (tab-->k)-->(j+COL_HSIZE);
572 (tab-->k)-->(i+COL_HSIZE) = v2;
573 (tab-->k)-->(j+COL_HSIZE) = v1;
574 if ((v1 == TABLE_NOVALUE) || (v2 == TABLE_NOVALUE))
575 TableSwapBlankBits(tab, i, j, k);
576 }
577];
Compare Rows.
TableCompareRows(T, C, R1, R2, D) returns:
(a) +1 if the entry at row R1 of column C is > the entry at row R2, (b) 0 if they are equal, and (c) -1 if entry at R1 < entry at R2.
When D = +1, a blank value is greater than all other values, so that in an ascending sort the blanks come last; when D = -1, a blank value is less than all others, so that once again blanks are last. Finally, a wholly blank row is always placed after a row in which the entry in C is blank but where other entries are not.
593[ TableCompareRows tab col row1 row2 dir val1 val2 bl1 bl2 f;
594 if (col >= 100) col=TableFindCol(tab, col, false);
595 val1 = (tab-->col)-->(row1+COL_HSIZE);
596 val2 = (tab-->col)-->(row2+COL_HSIZE);
597 if (val1 == TABLE_NOVALUE) bl1 = CheckTableEntryIsBlank(tab,col,row1);
598 if (val2 == TABLE_NOVALUE) bl2 = CheckTableEntryIsBlank(tab,col,row2);
599 if ((val1 == val2) && (bl1 == bl2)) {
600 if (val1 ~= TABLE_NOVALUE) return 0;
601 if (bl1 == false) return 0;
602
603 if (TableRowIsBlank(tab, row1)) {
604 if (TableRowIsBlank(tab, row2)) return 0;
605 return -1*dir;
606 }
607 if (TableRowIsBlank(tab, row2)) return dir;
608 return 0;
609 }
610 if (bl1) return dir;
611 if (bl2) return -1*dir;
612 f = ((tab-->col)-->1);
613 if (f & TB_COLUMN_ALLOCATED) {
614 if (BlkValueCompare(val2, val1) < 0) return 1;
615 return -1;
616 } else if (f & TB_COLUMN_REAL) {
617 if (REAL_NUMBER_TY_Compare(val1, val2) > 0) return 1;
618 return -1;
619 } else if (f & TB_COLUMN_SIGNED) {
620 if (val1 > val2) return 1;
621 return -1;
622 } else {
623 if (UnsignedCompare(val1, val2) > 0) return 1;
624 return -1;
625 }
626];
631[ TableMoveRowDown tab r1 r2 rx k l m v f;
632 if (r1==r2) return;
633 l = tab-->0;
634 for (k=1:k<=l:k++) {
635 f = false;
636 m = (tab-->k)-->(r1+COL_HSIZE);
637 if (m == TABLE_NOVALUE) f = true;
638 for (rx=r1:rx<r2:rx++) {
639 v = (tab-->k)-->(rx+COL_HSIZE+1);
640 (tab-->k)-->(rx+COL_HSIZE) = v;
641 if (v == TABLE_NOVALUE) f = true;
642 }
643 (tab-->k)-->(r2+COL_HSIZE) = m;
644 if (f) TableMoveBlankBitsDown(tab, r1, r2, k);
645 }
646];
Shuffle.
TableShuffle(T) sorts T into random row order.
652[ TableShuffle tab i to;
653 TableMoveBlanksToBack(tab, 1, TableRows(tab));
654 to = TableFilledRows(tab);
655 for (i=2:i<=to:i++) TableSwapRows(tab, i, random(i));
656];
Next Row.
TableNextRow(T, C, R, D) is used when scanning through a table in order of the values in column C: ascending order if D = 1, descending if D = -1. The current position is row R of column C, or R = 0 if we have not yet found the first row. The return value is the row number for the next value, or 0 if we are already at the final value. Note that if there are several equal values in the column, they will be run through in turn, in order of their physical row numbers - ascending if D = 1, descending if D = -1, so that using the routine with D = -1 always produces the exact reverse ordering from using it with D = 1 and the same parameters. Rows with blank entries in C are skipped.
for (R=TableNextRow(T,C,0,D): R : R=TableNextRow(T,C,R,D)) ...
will perform a loop of valid row numbers in order of column C.
675[ TableNextRow tab col row dir i k val v dv min_dv min_at signed_arithmetic f blk z;
676 if (col >= 100) col=TableFindCol(tab, col, false);
677 f = ((tab-->col)-->1);
678 if (f & TB_COLUMN_ALLOCATED) blk = true;
679 signed_arithmetic = f & TB_COLUMN_SIGNED;
680 #Iftrue (WORDSIZE == 2);
681 if (row == 0) {
682 if (signed_arithmetic) {
683 if (dir == 1) val = $8000; else val = $7fff;
684 } else {
685 if (dir == 1) val = 0; else val = $ffff;
686 }
687 } else val = (tab-->col)-->(row+COL_HSIZE);
688 if (signed_arithmetic) min_dv = $7fff; else min_dv = $ffff;
689 #ifnot;
690 if (row == 0) {
691 if (signed_arithmetic) {
692 if (dir == 1) val = $80000000; else val = $7fffffff;
693 } else {
694 if (dir == 1) val = 0; else val = $ffffffff;
695 }
696 } else val = (tab-->col)-->(row+COL_HSIZE);
697 if (signed_arithmetic) min_dv = $7fffffff; else min_dv = $ffffffff;
698 #endif;
699 k = TableRows(tab);
700 if (dir == 1) {
701 for (i=1:i<=k:i++) {
702 v = (tab-->col)-->(i+COL_HSIZE);
703 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,i)))
704 continue;
705 if (blk) {
706 dv = v;
707 if (row == 0) z = 1; else z = BlkValueCompare(v, val);
708 f = (((z > 0) || ((z == 0) && (i > row))) &&
709 ((min_at == 0) || (BlkValueCompare(v, min_dv) < 0)));
710 } else {
711 dv = dir*v;
712 if (signed_arithmetic)
713 f = (((dv > dir*val) || ((v == val) && (i>row))) &&
714 (dv < min_dv));
715 else
716 f = (((UnsignedCompare(dv, dir*val) > 0) || ((v == val) && (i>row))) &&
717 (UnsignedCompare(dv, min_dv) < 0));
718 }
719 if (f) { min_dv = dv; min_at = i; }
720 }
721 } else {
722 for (i=k:i>=1:i--) {
723 v = (tab-->col)-->(i+COL_HSIZE);
724 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,i)))
725 continue;
726 if (blk) {
727 dv = v;
728 if (row == 0) z = -1; else z = BlkValueCompare(v, val);
729 f = (((z < 0) || ((z == 0) && (i < row))) &&
730 ((min_at == 0) || (BlkValueCompare(v, min_dv) > 0)));
731 } else {
732 dv = dir*v;
733 if (signed_arithmetic)
734 f = (((dv > dir*val) || ((v == val) && (i<row))) &&
735 (dv < min_dv));
736 else
737 f = (((UnsignedCompare(dv, dir*val) > 0) || ((v == val) && (i<row))) &&
738 (UnsignedCompare(dv, min_dv) < 0));
739 }
740 if (f) { min_dv = dv; min_at = i; }
741 }
742 }
743 return min_at;
744];
749[ TableMoveBlanksToBack tab fromrow torow i fbl lnbl blc;
750 if (torow < fromrow) return;
751 fbl = 0; lnbl = 0;
752 for (i=fromrow: i<=torow: i++)
753 if (TableRowIsBlank(tab, i)) {
754 if (fbl == 0) fbl = i;
755 blc++;
756 } else {
757 lnbl = i;
758 }
759 if ((fbl>0) && (lnbl>0) && (fbl < lnbl)) {
760 TableMoveRowDown(tab, fbl, lnbl);
761 TableMoveBlanksToBack(tab, fbl, lnbl-1);
762 }
763 return torow-blc;
764];
Sort.
This is really only a front-end: it calls the sorting code at Sort.i6t.
770[ TableSort tab col dir test_flag algorithm i j k f;
771 for (i=1:i<=tab-->0:i++) {
772 j = tab-->i;
773 if ((j-->1) & TB_COLUMN_DONTSORTME)
774 return RunTimeProblem(RTP_TABLE_CANTSORT, tab);
775 }
776 if (col >= 100) col=TableFindCol(tab, col, false);
777 k = TableRows(tab);
778 k = TableMoveBlanksToBack(tab, 1, k);
779 if (test_flag) {
780 print "After moving blanks to back:^"; TableColumnDebug(tab, col);
781 }
782
783 SetSortDomain(TableSwapRows, TableCompareRows);
784 SortArray(tab, col, dir, k, test_flag, algorithm);
785
786 if (test_flag) {
787 print "Final state:^"; TableColumnDebug(tab, col);
788 }
789];
Print Table Name.
NI fills this in: it's used to say the "table" kind of value.
795[ PrintTableName T;
796 switch(T) {
797{-call:Tables::Support::compile_print_table_names}
798 default: print "** No such table **";
799 }
800];
Print Table to File.
This is how we serialise a table to an external file, though the writing is done by printing characters in the standard way; it's just that the output stream will be an external file rather than the screen when this routine is called.
809[ TablePrint tab i j k row col v tc kov;
810 for (i=1:i<=tab-->0:i++) {
811 j = tab-->i;
812 if (((j-->1) & TB_COLUMN_CANEXCHANGE) == 0)
813 rtrue;
814 }
815 k = TableRows(tab);
816 k = TableMoveBlanksToBack(tab, 1, k);
817 print " ", (PrintTableName) tab, " (", k, ")^";
818 for (row=1:row<=k:row++) {
819 for (col=1:col<=tab-->0:col++) {
820 tc = ((tab-->col)-->1) & TB_COLUMN_NUMBER;
821 kov = KindAtomic(TC_KOV(tc));
822 if (kov == UNKNOWN_TY) kov = NUMBER_TY;
823 v = (tab-->col)-->(row+COL_HSIZE);
824 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,row)))
825 print "-- ";
826 else {
827 if (BlkValueWriteToFile(v, kov) == false) print v;
828 print " ";
829 }
830 }
831 print "^";
832 }
833 rfalse;
834];
Read Table from File.
And this is how we unserialise again. It makes sense only on Glulx.
840#ifdef TARGET_GLULX;
841[ TableRead tab auxf row maxrow col ch v sgn dg j tc kov;
842 for (col=1:col<=tab-->0:col++) {
843 j = tab-->col;
844 if (((j-->1) & TB_COLUMN_CANEXCHANGE) == 0)
845 return RunTimeProblem(RTP_TABLE_CANTSAVE, tab);
846 }
847 maxrow = TableRows(tab);
848
849 for (row=1: row<=maxrow: row++) {
850 TableBlankOutRow(tab, row);
851 }
852 for (row=1: row<=maxrow: row++) {
853
854 ch = FileIO_GetC(auxf);
855 if (ch == '') {
856 while (ch ~= -1 or 10 or 13) ch = FileIO_GetC(auxf);
857 while (ch == 10 or 13) ch = FileIO_GetC(auxf);
858 }
859 for (col=1: col<=tab-->0: col++) {
860 if (ch == -1) { row++; jump NoMore; }
861 if (ch == 10 or 13) break;
862 tc = ((tab-->col)-->1) & TB_COLUMN_NUMBER;
863 kov = KindAtomic(TC_KOV(tc));
864 if (kov == UNKNOWN_TY) kov = NUMBER_TY;
865
866 sgn = 1;
867 if (ch == '-') {
868 ch = FileIO_GetC(auxf);
869 if (ch == -1) jump NotTable;
870 if (ch == '-') { ch = FileIO_GetC(auxf); jump EntryDone; }
871 sgn = -1;
872 }
873 if (((tab-->col)-->1) & TB_COLUMN_ALLOCATED)
874 ForceTableEntryNonBlank(tab, col, row);
875
876 v = BlkValueReadFromFile(0, 0, -1, kov);
877 if (v) {
878 if (((tab-->col)-->1) & TB_COLUMN_ALLOCATED)
879 v = BlkValueReadFromFile(TableLookUpEntry(tab, col, row),
880 auxf, ch, kov);
881 else
882 v = BlkValueReadFromFile(0, auxf, ch, kov);
883 ch = 32;
884 } else {
885 dg = ch - '0';
886 if ((dg < 0) || (dg > 9)) jump NotTable;
887 v = dg;
888 for (::) {
889 ch = FileIO_GetC(auxf);
890 dg = ch - '0';
891 if ((dg < 0) || (dg > 9)) break;
892 v = 10*v + dg;
893 }
894 v = v*sgn;
895 }
896
897 if (((tab-->col)-->1) & TB_COLUMN_ALLOCATED == 0)
898 TableLookUpEntry(tab, col, row, true, v);
899 .EntryDone;
900
901 while (ch == 9 or 32) ch = FileIO_GetC(auxf);
902 }
903 while (ch ~= -1 or 10 or 13) {
904 if ((ch ~= '-') && (((ch-'0')<0) || ((ch-'0')>9))) jump NotTable;
905 if (ch ~= 9 or 32) jump WontFit;
906 ch = FileIO_GetC(auxf);
907 }
908 }
909 .NoMore;
910 while (ch == 9 or 32 or 10 or 13) ch = FileIO_GetC(auxf);
911 if (ch == -1) return;
912 .WontFit;
913 return RunTimeProblem(RTP_TABLE_WONTFIT, tab);
914 .NotTable;
915 return RunTimeProblem(RTP_TABLE_BADFILE, tab);
916];
917#ENDIF;
Print Rank.
The table of scoring ranks is a residue from the ancient times of early IF: it gets a tiny amount of special treatment here, even though I7 works tend not to use these now dated conventions.
925[ PrintRank i j v;
926#ifdef RANKING_TABLE;
927 ANNOUNCE_SCORE_RM('B');
928 j = TableRows(RANKING_TABLE);
929 for (i=j:i>=1:i--)
930 if (score >= TableLookUpEntry(RANKING_TABLE, 1, i)) {
931 v = TableLookUpEntry(RANKING_TABLE, 2, i);
932 TEXT_TY_Say(v);
933 ".";
934 }
935#endif;
936 ".";
937];
Debugging.
Routines to print the state of a table, for debugging purposes only.
943[ TableColumnDebug tab col k i v tc kov;
944 if (col >= 100) col=TableFindCol(tab, col, false);
945 k = TableRows(tab);
946 tc = ((tab-->col)-->1) & TB_COLUMN_NUMBER;
947 kov = TC_KOV(tc);
948 for (i=1:i<=k:i++) {
949 if (i>1) print ", ";
950 v = (tab-->col)-->(i+COL_HSIZE);
951 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,i)))
952 print "--";
953 else {
954 PrintKindValuePair(kov, v);
955 }
956 }
957 say__p = 1;
958];
959
960[ TableRowDebug tab i col k v tc kov;
961 k = TableRows(tab);
962 if ((i<1) || (i>k)) "No such row";
963 print "(row ", i, ") |";
964 for (col=1: col<=tab-->0: col++) {
965 print " ";
966 tc = ((tab-->col)-->1) & TB_COLUMN_NUMBER;
967 kov = TC_KOV(tc);
968 v = (tab-->col)-->(i+COL_HSIZE);
969 if ((v == TABLE_NOVALUE) && (CheckTableEntryIsBlank(tab,col,i)))
970 print "-- ";
971 else {
972 PrintKindValuePair(kov, v);
973 print " |";
974 }
975 }
976 say__p = 1;
977];
978
979[ TableDebug tab i k;
980 PrintTableName(tab); print "^";
981 k = TableRows(tab);
982 for (i=1:i<=k:i++) { TableRowDebug(tab, i); print "^"; }
983];