Data Packages in Textual Inter. How static data, variables and constants are expressed in textual inter programs. @h Data packages. To recap from //Textual Inter//: an Inter program is a nested hierarchy of packages. Some are special |_code| packages which define functions; the rest we will call "data packages".[1] Note that the compulsory outer |main| package is a data package. The instructions which can appear in data packages are the subject of this section. [1] The term "data" is used rather loosely here. "Anything else packages" might be a fairer description. @h Variable and values. The instruction |variable| seems a good place to begin, since it creates an easily-understood piece of data. For example: = (text as Inter) variable V_score = 10 = declares a new variable |V_score|, and assigns it the initial value 10. This is a global variable, accessible across the whole program. @ A number of different notations are allowed as numerical values: (*) A decimal integer, which may begin with a minus sign (and, if so, will be stored as twos-complement signed); for example, |-231|. (*) A hexadecimal integer prefixed with |0x|, which can write the digits |A| to |F| in either upper or lower case form, but cannot take a minus sign; for example, |0x21BC|. (*) A binary integer prefixed with |0b|, which cannot take a minus sign; for example, |0b1001001|. (*) |r"text"| makes a literal real number: the text is required to use the same syntax as a literal real number in Inform 6. For example, |r"+1.027E+5"|. The |E+n| or |E-n| exponent is optional, but if it is used, a |+| or |-| sign is required; similarly, a |+| or |-| sign is required up front. So |r"1.0"| and |r"3.7E7"| are both illegal. Note that Inter does not specify the word size, that is, the maximum range of integers; many Inter programs are written on the assumption that this will be 16-bit and would fail if that assumption were wrong, or vice versa, but other Inter programs work fine whichever is the case. Real numbers, however, can only be used in 32-bit programs, and even then only have the accuracy of |float|, not |double|. @ There are also several forms of text: (*) Literal text is written in double quotes, |"like so"|. All characters within such text must have Unicode values of 32 or above, except for tab (9), written |\t|, and newline (10), written |\n|. In addition, |\"| denotes a literal double-quote, and |\\| a literal backslash, but these are the only backslash notations at present allowed. (*) |dw"text"| is meaningful only for interactive fiction, and represents the command parser dictionary entry for the word |text|. This is equivalent to the Inform 6 constant |'text//'|. (*) |dwp"text"| is the same, but pluralised, equivalent to Inform 6 |'text//p'|. @ There are two oddball value notations which should be used as little as possible: (*) |!undef| makes a special "this is not a value" value. (*) |glob"raw syntax"| is a feature allowing raw code for the final target language to be smuggled into Inter, which is supposedly target-independent. For example, |glob"#$magic"| says that the final code-generator should just print out |#$magic|, in blind faith that this will mean something, when it wants the value in question. Glob is not a respectful term, but this feature does not deserve respect, and is not used anywhere in the Inform tool chain. @h Constant and extended values. The instruction |constant| defines a name for a given value. For example: = (text as Inter) constant SPEED_LIMIT = 70 = The name of this constant can then be used wherever a value is needed. Thus: = (text as Inter) package main _plain constant SPEED_LIMIT = 70 variable V_speed = SPEED_LIMIT = @ Constants also allow us to write more elaborate values than are normally allowed -- so-called "extended values". In particular: (*) A literal |list| is written in braces: |{ V1, V2, ..., Vn }|, where |V1|, |V2| and so on are all (unextended) values. The empty list is |{ }|. (*) A list of bytes, rather than words, is written |bytes{ V1, V2, ..., Vn}|, in the same way. (*) Either sort of list can be given with an extent instead. |list of N words| or |list of N bytes| constructs a list of |N| zero entries. This is not simply an abbreviation for typing something like |{ 0, 0, 0, 0, 0, 0, 0, 0 }|, because |N| does not have to be a literal number -- it can be a named symbol defined elsewhere, or even defined in a different Inter tree to be linked in later. (*) Prefixing either sort of list with the keyword |bounded| tells Inter that the first entry (i.e., at index 0) should be the number of entries, not counting that first entry. (This number is the list's "bound".) Thus |bounded { 70, 15 }| is equivalent to |{ 2, 70, 15 }|, and |bounded list of 50 bytes| produces a list of 51 bytes, the first being 50, the next fifty all being 0. (*) A structure is written |struct{ V1, V2, ..., Vn }|. The empty |struct| is not legal, and the keyword |bounded| cannot be used. (*) Calculated values are written |sum{ V1, V2, ..., Vn }|, and similarly for |product{ }|, |difference{ }| and |quotient{ }|. Empty calculated values are not legal. (*) Finally, two special forms of list which are used only in interactive fiction projects, and whose semantics are identical to regular lists except for the special ways they are compiled: |grammar{ ... }| makes a list which is the command-parser grammar for a command verb, and |inline{ ... }| makes a list which is to be the value of a property compiled "inline". @ Readers with experience of Inform 6 will recognise that |{ ... }| and |bytes{ ... }| correspond to I6's |Array -->| and |Array ->| respectively, that |bounded { ... }| and |bounded bytes{ ... }| correspond to |Array table| and |Array buffer|, and that |list of N words| and |list of N bytes| correspond to |Array --> N| and |Array -> N|. Note, however, that Inter does not suffer from the ambiguity of Inform 6's old syntax here. The Inter list |{ 20 }| is unambiguously a one-entry list whose one entry is 20; it is quite different from |list of 20 words|. @ Lists are obviously useful. Here are some examples: = (text as Inter) constant squares = { 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 } constant colours = { "red", "green", "blue" } constant lists = { squares, colours } = The distinction between a |struct| and a |list| is only visible if typechecking is used (see below); the expectation is that a list would contain a varying number of entries all of the same type, whereas a struct would contain a fixed number of entries of perhaps different but predetermined types. @ Calculated values are an unusual but very useful feature of Inter. Consider: = (text as Inter) constant SPEED_LIMIT = 70 constant SAFE_SPEED = difference{ SPEED_LIMIT, 5 } = This effectively declares that |SAFE_SPEED| will be 65. What makes this useful is that when two Inter programs are linked together, |SAFE_SPEED| might be declared in one and |SPEED_LIMIT| in the other, and it all works even though the compiler of one could see the 70 but not the 5, and the compiler of the other could see the 5 but not the 70. @h URL notation. All identifier names are local to their own packages. So, for example, this: = (text as Inter) package main _plain package one _plain constant SPEED_LIMIT = 70 variable V_speed = SPEED_LIMIT package two _plain variable V_speed = 12 = is a legal Inter program and contains two different variables. But this: = (text as Inter) package main _plain package one _plain constant SPEED_LIMIT = 70 package two _plain variable V_speed = SPEED_LIMIT = ...does not work. The variable |V_speed| is declared in package |two|, where the constant |SPEED_LIMIT| does not exist. This might seem to make it impossible for material in one package to refer to material in any other, but in fact we can, using URL notation: = (text as Inter) package main _plain package one _plain constant SPEED_LIMIT = 70 package two _plain variable V_speed = /main/one/SPEED_LIMIT = Here |/main/one/SPEED_LIMIT| is an absolute "URL" of the symbol |SPEED_LIMIT|. If we return to the example: = (text as Inter) package main _plain package one _plain constant SPEED_LIMIT = 70 variable V_speed = SPEED_LIMIT package two _plain variable V_speed = 12 = we see that the two variables have different URLs, |/main/one/V_speed| and |/main/two/V_speed|. @h Annotations. A few of the defined names in Inter can be "annotated". Many annotations are simply markers temporarily given to these names during the compilation process, and they usually do not change the meaning of the program. For example, the final C code generator annotates the names of arrays with their addresses in (virtual) memory, with the |__array_address| annotation. In textual format: = (text as Inter) constant my_array = { 1, 2, 4, 8 } __array_address=7718 = All annotation names begin with a double underscore, |__|. They do not all express a value: some are boolean flags, where no |=...| part is written. For the list of standard annotation names in use, see //Inform Annotations//. @h Metadata constants. If constant names begin with the magic character |^| then they represent "metadata", describing the program rather than what it does. They are not data in the program at all. Thus: = (text as Inter) constant ^author = "Jonas Q. Duckling" = is legal, but: = (text as Inter) constant ^author = "Jonas Q. Duckling" variable V_high_scorer = ^author = is not, because it tries to use a piece of metadata as if it were data. @h Types in Inter. Inter is an exceptionally weakly typed language. It allows the user to choose how much type-checking is done. Inter assigns a type to every constant, variable and so on. But by default those types are always a special type called |unchecked|, which means that nothing is ever forbidden. This is true even if the type seems obvious: = (text as Inter) constant SPEED_LIMIT = 20 = gives |SPEED_LIMIT| the type |unchecked|, not (say) |int32|. If a storage object such as a variable has type |unchecked|, then anything can be put into it; and conversely an |unchecked| value can always be used in any context. So if we want a constant or variable to have a type, we must give it explicitly: = (text as Inter) constant (int32) SPEED_LIMIT = 20 variable (text) WARNING = "Slow down." = The "type marker" |(int32)|, which is intended to look like the C notation for a cast, gives an explicit type. The following, however, will be rejected: = (text as Inter) constant (int32) SPEED_LIMIT = 20 variable (text) WARNING = SPEED_LIMIT = This is because |WARNING| has type |text| and cannot hold an |int32|. This is typechecking in action, and although you must volunteer for it, it is real. By conscientiously applying type markers throughout your program, you can use Inter as if it were a typed language. @ An intentional hole in this type system is that literals which look wrong for a given type can often be used as them. This, for instance, is perfectly legal: = (text as Inter) constant (text) SPEED_LIMIT = 20 variable (int32) WARNING = "Slow down." = The type of a constant or variable is always either |unchecked| or else is exactly what is declared in brackets, regardless of what the value after the equals sign looks as if it ought to be. However, a weaker form of checking is actually going on under the hood: numerical data has to fit. So for example: = (text as Inter) constant (int2) true = 1 constant (int2) false = 0 constant (int2) dunno = 2 = allows |true| and |false| to be declared, but throws an error on |dunno|, because 2 is too large a value to be stored in an |int2|. Even this checking can be circumvented with a named constant of type |unchecked|, as here: = (text as Inter) constant dangerous = 17432 constant (int2) safe = dangerous = This is allowed, and the result may be unhappy, but the user asked for it. @ Types are like values in that simple ones can be used directly, but to make more complicated ones you need to give them names. The analogous instruction to |constant|, which names a value, is |typename|, which names a type. The basic types are very limited: |int2|, |int8|, |int16|, |int32|, |real| and |text|. These are all different from each other, except that an |int16| can always be used as an |int32| without typechecking errors, but not vice versa; and so on for other types of integer. Note that Inter takes no position on whether or not these are signed; the literal |-6| would be written into an |int8|, an |int16| or an |int32| in a twos-complement signed way, but Inter treats all these just as bits. With just five types it really seems only cosmetic to use |typename|: = (text as Inter) typename boolean = int2 constant (boolean) true = 1 variable (boolean) V_flag = true typename truth_state = boolean = But what brings |typename| into its own is that it allows the writing of more complex types. For example: = (text as Inter) typename bit_stream = list of int2 constant (bit_stream) signal = { 1, 0, 1, 1, 0, 1 } variable (bit_stream) V_buffer = signal = |list of T| is allowed only for simple types |T|, so |list of list of int32|, say, is not allowed: but note that a typename is itself a simple type. So: = (text as Inter) typename bit_stream = list of int2 typename signal_list = list of bit_stream constant (bit_stream) signal1 = { 1, 0, 1, 1, 0, 1 } constant (bit_stream) signal2 = { } constant (bit_stream) signal3 = { 0, 1, 1 } constant (signal_list) log = { signal1, signal2, signal3 } variable (signal_list) V_buffer = log = will create a variable whose initial contents are a list of three lists of |int2| values. @ The "type constructions" allowed are as follows: (*) |list of T| for any simple type or typename |T|; (*) |function T1 T2 ... Tn -> T| for any simple types |T1|, |T2|, and so on. In the special case of no arguments, or no result, the notation |void| is used, but |void| is not a type. (*) |struct T1 T2 ... Tn| for any simple types |T1|, |T2|, and so on. There must be at least one of these, so |struct void| is not allowed. (*) |enum|, for which see below; (*) and then a raft of constructions convenient for Inform but which Inter really knows nothing about: |activity on T|, |column of T|, |table of T|, |relation of T1 to T2|, |description of T|, |rulebook of T|, and |rule T1 -> T2|. Perhaps these ought to work via a general way for users to create new constructors, but for now they are hard-wired. They do nothing except to be distinct from each other, so that Inform can label its data. Inter applies the usual rules of covariance and contravariance when matching these types. For example: (*) |list of int2| matches |list of int32| but not vice versa (covariance in the entry type); (*) |function int32 -> void| matches |function int2 -> void| but not vice versa (contravariance in argument types); (*) |function text -> int2| matches |function text -> int32| but not vice versa (covariance in result types). @ This enables us to declare the type of a function. A typed version of |Hello| might look like this: = (text as Inter) package main _plain typename void_function = function void -> void package (void_function) Main _code code inv !enableprinting inv !print val "Hello, world.\n" = And similarly: = (text as Inter) typename ii_i_function = function int32 int32 -> int32 package (ii_i_function) gcd _code ... = creates a function called |gcd| whose type is |int32 int32 -> int32|. Note that only |_code| packages are allowed to be marked with a type, because only |_code| package names are values. @ As an example of structures: = (text as Inter) typename city_data = struct real real text constant (city_data) L = struct{ r"+51.507", r"-0.1275", "London" } constant (city_data) P = struct{ r"+48.857", r"+2.3522", "Paris" } = @h Enumerations and instances. That leaves enumerations, which have the enigmatically concise type |enum|. Only a typename can have this type: it may be concise but it is not simple. (So |list of enum| is not allowed.) |enum| is special in that each different time it is declared, it makes a different type. For example: = (text as Inter) typename city = enum typename country = enum typename nation = country = Here there are two different enumerated types: |city| and another one which can be called either |country| or |nation|. As in many programming languages, an enumerated type is one which can hold only a fixed range of values known at compile time: for example, perhaps it can hold only the values 1, 2, 3, 4. An unusual feature of Inter is that the declaration does not specify these permitted values. Instead, they must be declared individually using the |instance| instruction. For example: = (text as Inter) typename city = enum instance (city) Berlin instance (city) Madrid instance (city) Lisbon = For obvious reasons, the type marker -- in this case |(city)| -- is compulsory, not optional as it was for |constant|, |variable| and |package| declarations. At runtime, the values representing these instances are guaranteed to be different, but we should not assume anything else about those values. The final code-generator may choose to number them 1, 2, 3, but it may not. (When enumerations are used by the Inform 7 tool-chain for objects, the runtime values will be object IDs in the Z-machine or pointers to objects in Glulx or C, for instance.) If we need specific numerical values (which must be non-negative), we can specify that explicitly: = (text as Inter) typename city = enum instance (city) Berlin = 1 instance (city) Madrid = 17 instance (city) Lisbon = 201 = You should either specify values for all instances of a given enumeration, or none. Note that instances do not have to be declared in the same package, or even the same program, as the enumeration they belong to. @h Subtypes. Enumerated types, but no others, can be "subtypes". For example: = (text as Inter) typename K_thing = enum typename K_vehicle <= K_thing typename K_tractor <= K_vehicle = An instance of |K_tractor| is now automatically also an instance of |K_vehicle|, but the converse is not necessarily true. The right-hand side of the |<=| sign is only allowed to be an enumerated typename, and a new typename created in this way is, for obvious reasons, also enumerated. @h Properties. Inter supports a simple model of properties and values. (An enumerated typename is in effect a class, and this is why instances are so called.) A property is a set of similarly-named variables belonging, potentially, to any number of owners, each having their own value. As with constants and variables, properties can optionally have types. For example: = (text as Inter) property population property (text) motto = Any instance can in principle have its own copy of any property, and so can an enumerated type as a whole. But this is allowed only if an explicit permission is granted: = (text as Inter) typename city = enum instance (city) Stockholm instance (city) Odessa permission for city to have population permission for Odessa to have motto = And we can now use the |propertyvalue| instruction to set these: = (text as Inter) propertyvalue population of Stockholm = 978770 propertyvalue population of Odessa = 1015826 propertyvalue motto of Odessa = "Pearl of the Black Sea" = @ An optional extended form of |permission| is allowed which enables us to say that we want the storage for a property to be in a given list. Thus: = (text as Inter) constant population_storage = { 2, 978770, 1015826 } typename city = enum instance (city) Stockholm instance (city) Odessa property population permission for city to have population population_storage = But this is finicky, and has to be set up just right in order to work.[1] [1] The feature exists in Inter because of Inform 7's ability to define kinds with tables, so that the storage lists are the columns of the table in question. Because I7 allows those properties to be modified or read either qua properties or qua table entries, we cannot avoid giving Inter a similar ability, even though we might prefer not to. @h Insert. Never use |insert|. @ Well, okay then. This exists to implement very low-level features of Inform 7, going back to its earliest days as a programming language, when people were still writing strange hybrid programs partly in I6. |insert| tells Inter that it needs to add this raw I6-syntax material to the program: = (text as Inter) insert "\n[ LITTLE_USED_DO_NOTHING_R; rfalse; ];\n" = @h Splats. And never use |splat| either. @ Well, okay then. We do in fact temporarily make splats when compiling kit source, written in Inform 6 syntax, into Inter. During that process, there are times when the source code is only partially digested. Each individual I6-syntax directive is converted into a "splat" holding its raw text. But this is then later translated into better Inter, and the splat removed again. For details, if you really must, see //bytecode: The Splat Construct//. The name "splat" is chosen as a psychological ploy, to make people feel queasy about using this. See also "glob" above, which is the analogous construction for values rather than void-context material.