Section 2: The language of data structures

Contents Back Forward | ### 2. The language of data structures
| ### 2.1. Directives and constants Every example program so far has consisted only of a sequence of routines, each within beginning and end markers `[` and `]`. Such routines have no way of communicating with each other, and therefore of sharing information with each other, except by making function calls back and forth. This arrangement is not really suited to a large program whose task may be to simulate something complicated (such as the world of an adventure game): it would be useful to have some kind of central registry of information which all routines have access to, as and when needed. Information available to all routines in this way is said to be "global'', rather than "local'' to any one routine. (As will appear in Section 3, there is also an intermediate possibility where information is available only to a cluster of routines working on roughly the same part of a program.) This global information can be organised in a variety of ways. Such organised groups are called "data structures''. For example, a typical data structure might be a list of 10 values. The term "data structure'' did not appear in Section 1 because information was only ever held in variables, the simplest possible kind of structure (one value on its own). Data structures are added to Inform programs using commands called "directives'' in between definitions of routines. It's important to distinguish between these, which direct Inform to do something now (usually, to create something) and the statements which occur inside routines, which are merely translated in some way but not acted on until the program has finished being compiled and is run. In fact, one directive has already appeared: the one written `[`, which means "translate the following routine up to the next `]`''. In all there are 38 Inform directives, as follows:
    Abbreviate  Array       Attribute  Class        Constant     Default
    Dictionary  End         Endif      Extend       Fake_action  Global
    Ifdef       Ifndef      Ifnot      Ifv3         Ifv5         Iftrue
    Iffalse     Import      Include    Link         Lowstring    Message
    Nearby      Object      Property   Release      Replace      Serial
    Switches    Statusline  Stub       System_file  Trace        Verb
    Version     [
Several of these are rather technical and will not be used by many programmers (such as `Trace`, `Stub`, `Default`, `System_file`, `Abbreviate`, `Dictionary`). Others control fine points of what is compiled and what isn't (`Ifdef`, `Ifnot`, and so on; `Message`, `Replace`). These not-very important directives are covered in Chapter II. This leaves 9 central directives for creating data structures, and these are the ones which it is important to know about:
    Array       Attribute   Class      Constant     Extend       Global
    Object      Property    Verb
It is conventional to write these with the initial letter capitalised: this makes directives look unlike statements. `Attribute`, `Class`, `Object` and `Property` are the subject of Section 3. The simplest directive with a "global'' effect on the program -- an effect all over the program, that is, not just in one routine -- is `Constant`. The following program, an unsatisfying game of chance, shows a typical use of `Constant`.
        Constant MAXIMUM_SCORE = 100;

        [ Main;
          print "You have scored ", random(MAXIMUM_SCORE),
          " points out of ", MAXIMUM_SCORE, ".^";
        ];
The maximum score value is used twice in the routine `Main`. Of course the program is the same as it would have been if the constant definition were not present, and `MAXIMUM_SCORE` were replaced by 100 in both places where it occurs. The advantage of using `Constant` is that it makes it possible to change this value from 100 to, say, 50 with only a single change, and it makes the source code more legible to the reader by explaining what the significance of the number 100 is supposed to be. If no value is specified for a constant, as in the line
        Constant DEBUG;
then the constant is created with value 0. ### 2.2. Global variables As commented above, so far the only variables allowed have been "local variables'', each private to their own routines. A "global variable'' is a variable which is accessible to all code in every routine. Once a global variable has been declared, it is used in just the same way as a local variable. The directive for declaring a global variable is `Global`:
        Global score = 36;
This creates a variable called `score`, which at the start of the program has the value 36. `score` can be altered or used anywhere in the program after the line on which it is defined. ### 2.3. Arrays An "array'' is an indexed collection of (global) variables, holding a set of numbers organised into a sequence. It allows general rules to be given for how a group of variables should be treated. For instance, the directive
        Array pack_of_cards --> 52;
creates a stock of 52 variables, referred to in the program as
        pack_of_cards-->0   pack_of_cards-->1   ...   pack_of_cards-->51
There are two basic kinds of array: "word arrays'' (written using `-->` as above) and "byte arrays'' (written using `->` similarly). Whereas the entries of a word array can hold any number, the entries of a byte array can only be numbers in the range 0 to 255 inclusive. (The only advantage of this is that it is more economical on memory, and beginners are advised to use word arrays instead.) In addition to this, Inform provides arrays which have a little extra structure: they are created with the 0th entry holding the number of entries. A word array with this property is called a `table`; a byte array with this property is a `string`. For example, the array defined by
        Array continents table 5;
has six entries: `continents-->0`, which holds the number 5, and five more entries, indexed 1 to 5. (The program is free to change `continents-->0` later but this will not change the size: the size of an array can never change.) As an example of using `string` arrays:
        Array password string "DANGER";
        Array phone_number string "1978-345-2160";
        ...
        PrintString(password);
        ...
        PrintString(phone_number);
        ...
        [ PrintString the_array i;
            for (i=1: i<=the_array->0: i++)
                print (char) the_array->i;
        ];
The advantage of `string` arrays, then, is that one can write a general routine like `PrintString` which works for arrays of any size. To recapitulate, Inform provides four kinds of array in all:
        -->   ->   table   string
There are also four different ways to set up an array with its initial contents (so the directive can take 16 forms in all). In all of the examples above, the array entries will all contain 0 when the program begins. Instead, we can give a list of constant values. For example,
        Array primes --> 2 3 5 7 11 13;
is a word array created with six entries, `primes-->0` to `primes-->5`, initially holding the values 2 to 13. The third way to create an array gives some text as an initial value (because one common use for arrays is as "strings of characters'' or "text buffers''). The two string arrays above were set up this way. As another example,
        Array players_name -> "Frank Booth";
sets up the byte array `players_name` as if the directive had been
        Array players_name -> 'F' 'r' 'a' 'n' 'k' ' ' 'B' 'o' 'o' 't' 'h';
| The fourth way to create an array is obsolete and is kept only so that old programs still work. This is to give a list of values in between end-markers `[` and `]`, separated by commas or semi-colons. Please don't use this any longer.
| WARNING: It is up to the programmer to see that no attempt is made to read or write non-existent entries of an array. (For instance, `pack_of_cards-->1000`.) Such mistakes are notorious for causing programs to fail in unpredictable ways, difficult to diagnose. Here for example is an erroneous program:
        Array ten --> 10;
        Array fives --> 5 10 15 20 25;
        [ Main n;
          for (n=1: n<=10: n++) ten-->n = -1;
          print fives-->0, "^";
        ];
This program ought to print 5 (since that's the 0-th entry in the array `fives`), but in fact it prints -1. The problem is that the entries of `ten` are `ten-->0` up to `ten-->9`, not (as the program implicitly assumes) `ten-->1` to `ten-->10`. So the value -1 was written to `ten-->10`, an entry which does not exist. At this point anything could have happened. As it turned out, the value was written into the initial entry of the next array along, "corrupting'' the data there. ### 2.4. Example 7: Shuffling a pack of cards This program simulates the shuffling of a pack of playing cards. The cards are represented by numbers in the range 0 (the Ace of Hearts) to 51 (the King of Spades). The pack itself has 52 positions, from position 0 (on the top) to position 51 (on the bottom). It is therefore represented by the array
        pack_of_cards-->i
whose `i`-th entry is the card number at position `i`. A new pack as produced by the factory, still in order, would therefore be represented with card `i` in position `i`: the pack would have the Ace of Hearts on top and the King of Spades on the bottom.
        Constant SHUFFLES = 100;
        Array pack_of_cards --> 52;

        [ ExchangeTwo x y z;

          !   Initially x and y are both zero

          while (x==y)
          {   x = random(52) - 1; y = random(52) - 1;
          }

          !   x and y are now randomly selected, different numbers
          !   in the range 0 to 51

          z = pack_of_cards-->x;
          pack_of_cards-->x = pack_of_cards-->y;
          pack_of_cards-->y = z;
        ];

        [ Card n;
          switch(n%13)
          {   0: print "Ace";
              1 to 9: print n%13 + 1;
              10: print "Jack";
              11: print "Queen";
              12: print "King";
          }
          print " of ";
          switch(n/13)
          {   0: print "Hearts";
              1: print "Clubs";
              2: print "Diamonds";
              3: print "Spades";
          }
        ];

        [ Main i;
          !   Create the pack in "factory order":
          for (i=0:i<52:i++) pack_of_cards-->i = i;
          !   Exchange random pairs of cards for a while:
          for (i=0:i<SHUFFLES:i++) ExchangeTwo();
          print "The pack has been shuffled to contain:^";
          for (i=0:i<52:i++)
              print (Card) pack_of_cards-->i, "^";
        ];
Note the use of a "printing rule'' called `Card` to describe card number `i`. Note also that 100 exchanges of pairs of cards is only just enough to make the pack appear well shuffled. Redefining `SHUFFLES` as 10000 makes the program take longer; redefining it as 10 makes the result very suspect.
| The example code shuffles the pack in a simple way, but there are more efficient methods. Here's one supplied by Dylan Thurston, giving perfect randomness in 51 exchanges.
            pack_of_cards-->0 = 0;
            for (i=1:i<52:i++)
            {   j = random(i+1) - 1;
                pack_of_cards-->i = pack_of_cards-->j; pack_of_cards-->j = i;
            }
| ### 2.5. Seven special data structures
| All Inform programs automatically contain seven special data structures, each being one of a kind: the object tree, the grammar, the table of actions, the release number, the serial code, the "statusline flag'' and the dictionary. These data structures are tailor-made for adventure games and (except for the object tree) can be ignored for every other kind of program. So they are mostly covered in Book Two.
| 1. -- For the object tree (and the directives `Object` and `Class`), see Section 3. 2. -- For grammar (and the directives `Verb` and `Extend`), see Chapter V, Section 26 and Section 27. 3. -- For actions (and the `<...>` and `<<...>>` statements and the `##` constant notation), see Chapter III, Section 9. 4. -- The release number (which is printed automatically by the library in an Inform-written adventure game) is 1 unless otherwise specified. The directive
        Release <number>;
-- does this. Conventionally release 1 would be the first published copy, and releases 2, 3, ... would be amended re-releases. See Chapter III, Section 7, for an example. 5. -- The serial number is set automatically to the date of compilation in the form 960822 ("22nd August 1996''). This can be overridden if desired with the directive
        Serial "dddddd";
-- where the text must be a string of 6 digits. 6. -- The "status line flag'' chooses between styles of "status line'' at the top of an adventure game's screen display. See Chapter IV, Section 18, for use of the `Statusline` directive. 7. -- The dictionary is automatically built by Inform. It is a stock of all the English words which the game might want to recognise from what the player has typed: it includes any words written in constants like `'duckling'`, as well as any words given in `name` values or in grammar. For example
        if (first_word == 'herring') print "You typed the word herring!";
-- is a legal statement. Inform notices that `herring` -- because it is in single quotes -- is a word the program may one day need to be able to recognise, so it adds the word to the dictionary. Note that the constant `'herring'` is a dictionary word but the constant `'h'` is the ASCII value of lower-case H. (Single-letter dictionary words are seldom needed, but can be written using an ugly syntax if need be: `#n$h` is the constant meaning "the dictionary word consisting only of the letter H''.)
| From this description, the dictionary appears to be something into which words are poured, never to re-emerge. The benefit is felt when the `read` statement comes to try to parse some input text:
        read text_array parse_buffer;
It must be emphasized that the `read` statement performs only the simplest possible form of parsing, and should not be confused with the very much more elaborate parser included in the Inform library.
| What it does is to break down the line of input text into a sequence of words, in which commas and full stops count as separate words in their own right. (An example is given in Chapter V, Section 24.) Before using `read`, the entry
        parse_buffer->0
should be set to the maximum number of words which parsing is wanted for. (Any further words will be ignored.) The number of words actually parsed from the text is written in
        parse_buffer->1
and a block of data is written into the array for each of these words:
        parse_buffer-->(n*2 - 1)
holds the dictionary value of the `n`-th word (if `n` counts 1, 2, 3, ...). If the word isn't in the dictionary, this value is zero. (In addition,
        parse_buffer->(n*4)
        parse_buffer->(n*4 + 1)
are set to the number of letters in word `n`, and the offset of word `n` in the text array.) For example,
        [ PleaseTypeYesOrNo i;
          for (::)
          {   buffer->0 = 60;
              parse->0 = 1;
              print "Please type ~yes~ or ~no~> ";
              read buffer parse;
              if (parse-->1 == 'yes') rtrue;
              if (parse-->1 == 'no')  rfalse;
          }
        ];

Contents / Back / Forward
Chapter I / Chapter II / Chapter III / Chapter IV / Chapter V / Chapter VI / Appendix

Mechanically translated to HTML from third edition as revised 16 May 1997. Copyright © Graham Nelson 1993, 1994, 1995, 1996, 1997: all rights reserved.