I6 Template Layer

Inform 7 6M62ContentsIntroductionFunction IndexRules Index

Tables.i6t

Tables contents

Format.

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; ! Mask to remove upper bit flags 37 38Constant COL_HSIZE 2; ! Column header size: two words (ID/flags, blank bits)

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    ! Read blank bit for row1: 286    at = ((tab-->col)-->2) + (row1/8); 287    bit = ((TB_Blanks->at) & (CheckTableEntryIsBlank_LU->(row1%8))); 288    if (bit) bit = true; 289    ! Loop through, setting each blank bit to the next: 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    ! Write bit to blank bit for row2: 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    ! print "Giving up^"; 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); !print i, " rows^"; 529    for (j=1:j<=i:j++) 530        if (TableRowIsBlank(tab, j)) c++; 531    !print c, " blank^"; 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        ! The two entries are both blank: 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];

Move Row Down.

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; ! WORDSIZE == 4 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];

Move Blanks to Back.

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); ! Move first blank just past last nonblank 761        TableMoveBlanksToBack(tab, fbl, lnbl-1); 762    } 763    return torow-blc; ! Final non-blank row 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; ! Address of column table 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; ! Address of column table 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; ! Address of column table 844        if (((j-->1) & TB_COLUMN_CANEXCHANGE) == 0) 845            return RunTimeProblem(RTP_TABLE_CANTSAVE, tab); 846    } 847    maxrow = TableRows(tab); 848    !print maxrow, " rows available.^"; 849    for (row=1: row<=maxrow: row++) { 850        TableBlankOutRow(tab, row); 851    } 852    for (row=1: row<=maxrow: row++) { 853        !print "Reading row ", row, "^"; 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            !print "tc = ", tc, " kov = ", kov, "^"; 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            !print "A"; 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            !print "v=", v, " "; 897            if (((tab-->col)-->1) & TB_COLUMN_ALLOCATED == 0) 898                TableLookUpEntry(tab, col, row, true, v); 899            .EntryDone; 900            !print "First nd is ", ch, "^"; 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; ! TARGET_GLULX

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];