The Archetype Language Reference Manual by Derek T. Jones Overview An Archetype program consists of a series of object declarations. These declarations can be simplified by defining "types" so that the common attributes of several slightly different objects can be described once; this is known as "inheritance" in object- oriented parlance. Objects consist of changeable attributes, which describe an object's properties, and methods, which describe the object's behavior and how it depends on its own attributes and its interaction with other objects. Methods are tied to messages; when the object receives that message, the method associated with it is invoked. Methods consist of one or more statements tied to a message. To "invoke" a method means to execute its statements in order from top to bottom. Some statements have a value; the value of the last statement to be executed is returned to the sender of the message that invoked the method. Archetype programs are executed by the PERFORM program after being translated into an intermediate binary form by the CREATE program. The PERFORM program contains a single object named "system" which starts your program by sending the message 'START' to an object named "main". For this reason every Archetype program has to have one (and only one) object named "main" which contains a method for the 'START' message. Object Declarations An object declaration has the following form: ( | null ) ( : )* [ methods ( : )* ] end A typical object declaration: room bare_cell desc : "bleak, bare cell" methods 'look' : write "I'm standing in a ", desc, "." 'north' : hallway end In the above example, the type "room" had already been defined. A type must be defined before it is used by an object. The only type that is already defined for you is the "null" type. The "null" type means that the only attributes and methods that the object contains are what appears in its particular declaration. Whenever an object is declared to be of a certain type, that object inherits the attributes and methods declared in the definition of the type. Please NOTE that when an attribute has a default expression, as above, the attribute contains the unevaluated form of the expression. This is in contrast to what happens when the attribute is assigned a new value at run-time with the := operator (see Operators and Expressions below). What this means is that in an object declaration such as: null abc a : 3 def : a + 5 end the value of the attribute "def" will change if the value of attribute "a" changes. When an object inherits an attribute or method, it means that unless that object specifically redefines that attribute or method, it will have the attribute or method defined by the type. Look at the example below: type shouter based on null name : "an unremarkable shouting object" methods 'shout' : >>Shout, Shout, Ready Or Not! 'who are you' : write "I am ", name, "." end shouter Bob name : "Bob" end The object Bob did not declare the method 'shout' or the method 'who are you', but inherited them from the "shouter" declaration because it is a "shouter" type. It did not inherit the "name" attribute because it had its own specific declaration for that. The code above could be replaced by the single declaration below: null Bob name : "Bob" methods 'shout' : >>Shout, Shout, Ready Or Not! 'who are you' : write "I am ", name, "." end For a single object, the code above seems smaller and simpler, and perhaps it is. But if you had even two "shouter" objects, using a common type definition saves you time, makes the code clearer, and actually uses less memory while being executed. Type definitions can inherit attributes and methods from other type definitions. In the above example, type "shouter" was based on "null", meaning that there was no inheritance. But we could define a type "screamer" based on "shouter": type screamer based on shouter IsAscreamer : TRUE methods 'scream' : >>I CAN SCREAM TOO! end Now if we make an object of that type: screamer Joe name : "Joe" methods 'sing' : >>Tra la la! end it is the same as if we had declared Joe as: null Joe IsAscreamer : TRUE name : "Joe" methods 'sing' : >>Tra la la! 'scream' : >>I CAN SCREAM TOO! 'shout' : >>Shout, Shout, Ready Or Not! 'who are you' : write "I am ", name, "." end The object "Joe" inherits attributes and methods first from the "screamer" type, and then, because "screamer" is based on "shouter", from the "shouter" type. The trick of the "IsAscreamer" attribute is a common one in Archetype. If every type declares an attribute named IsA, it is easy to tell whether an object's particular "family tree" has that type in it: if any_object.IsAshouter then write any_object.desc, " is a shouter." Statements An Archetype method is a single statement. However, this statement can be compound, that is, made up of several statements inside curly braces { } . For example: null obj methods 'ex1' : write "This is a single statement." 'ex2' : { write "This is still a single statement because" write "both these statements are inside curly braces." } 'ex3' : write "Although this might look like a compound statement," write "it is not; this line and the next will be flagged" write "as errors by the CREATE program." end The 'ex3' method is only tied to the first statement. The next two statements are "stranded" and will prevent the program from being compiled into an executable form. Reference To Archtype Statements How To Read This Manual: Each statement is preceded by a single line followed by its complete syntax in Backus-Naur form. If you are not familiar with Backus-Naur form, read Appendix A. include "filename" This is probably the first Archetype statement you will use, in order to include STANDARD.ACH. The filename must be a string literal enclosed in double quotes. You can put a pathname on the include file if you like. The compiler will first search the current directory for the file and then the directory in which the compiler is located. All of the text in the file is compiled as though you had included it there with your editor. ( write | writes | stop ) [ (, )* ] The write, writes, and stop statements all take zero or more comma-separated expressions and write them to the screen. The write statement finishes off the line after writing its expressions. The writes statement keeps the line "dangling" so that subsequent output appears on the same line. The stop statement halts the Archetype program after writing its output and returns to DOS. All Archetype output is word-wrapped and "paged". No words will be split across the right margin, and text will not flow off the top of the screen before the user can read it all. After 23 lines of text, a "Hit any key to continue" message is displayed and the rest of the text is shown after the key is hit. In other words, all text output is "civilized" without the programmer having to program specifically for it. Examples: drinkable poison desc : "golden goblet" filled : TRUE methods 'look' : { writes "I look inside the ", desc, " and see " if filled then write "A frothing, noxious potion!" else write "nothing." } 'drink' : if filled then stop "I drink it down... Garg! I die in agony." else write "The ", desc, " is empty." end object manual desc : "programmer's manual" location : desk methods 'look' : write "As I look at the manual, I see that it has ", "a useful tip on the use of a single write ", "statement to make a block of free-flowing text ", "that is guaranteed to wrap correctly: using a ", "series of comma-separated strings." end Returns: The write statements always return the value of the last they write. if then [ else ] The if statement is extremely important because it is one of the only ways of modifying an object's behavior based on any of its attributes. is evaluated; if it is UNDEFINED, ABSENT, or FALSE, the statement following else is executed; otherwise the statment following then is executed. If is UNDEFINED, ABSENT, or FALSE and there is no else branch in the if statement, nothing happens. There must always be a then branch. The following then or else can be another if statement. One thing to be careful of when having another if statement follow a then is that the next else encountered will be considered a part of the most recent then. If this is not what you want, use curly braces to surround the inner if statement. Examples: object airlock open : TRUE locked : TRUE methods 'leave' : if not spacesuit.wearing then stop "I'm not wearing a spacesuit! I die." else if not spacesuit.patched then stop "All the air escapes through the hole in the suit!" else if not helmet.wearing then stop "I'm not wearing a helmet! I die." else { write "I step out of the airlock." message --> object } end Returns: The if expression returns the value of the last executed . If there is no else branch, and is UNDEFINED, ABSENT, or FALSE, then the value of the if statement is UNDEFINED. case of { ( : )* [ default : ] } The case statement is a way of checking an expression to see if it matches one of several values. is evaluated only once. Then the : pairs following the word of and a curly brace are evaluated from first to last until one of the s matches . If this happens, the following the colon is executed. The word default matches any and therefore must be at the end of the case statement. The case statment usually replaces a more complicated if-then-else-if... structure. Example: Instead of { tries +:= 1 if tries = 1 then write "I try to open it with my bare hands, but I can't." else if tries = 2 then write "I try and try but it stays closed." else if tries = 3 then write "I scream with frustration, but the can won't open." else if tries = 4 then write "I pound the can upon the pavement. Stays shut." else write "No. I've given up." } you can write case tries +:= 1 of { 1 : write "I try to open it with my bare hands, but I can't." 2 : write "I try and try but it stays closed." 3 : write "I scream with frustration, but the can ", "won't open." 4 : write "I pound the can upon the pavement. Stays shut." default : write "No. I've given up." } while do The while statement is the most straightforward way to do something repeatedly. is evaluated; if it is UNDEFINED, ABSENT, or FALSE, the while statment ends. If not, is executed and is evaluated again. There are two ways to stop a while loop: must eventually cause to evaluate to UNDEFINED, ABSENT, or FALSE, or must contain the break statment. Note that if is UNDEFINED, ABSENT, or FALSE before entering the while loop, will never be evaluated. Examples: { # sums up the numbers between 1 and x while x > 0 do { sum +:= x x -:= 1 } write "The sum is ", sum } # interactive questioning while TRUE do { writes "What message would you like to send? " if (m := read) == "quit" then break else m -> obj } Note that in the above example, the only way the loop can exit is with the break statement, since will always be TRUE. for do The for statement is the other way of doing things repeatedly. It is used when you want to do some action to all or some set of the objects in your program. should contain the keyword each. This keyword is set, in turn, to each object in your program. Every time does not evaluate to UNDEFINED, ABSENT, or FALSE, is executed. will usually contain the keyword each as well, which retains its value until is evaluated again. Examples: object purse methods 'empty' : { write "I empty out the purse and find:" for each.location = purse do { write each.desc each.location = player.location; 'MOVE' -> each } } end If you want to apply to every single object in your program, not just those that satisfy some , write for each do ... Note that even if only specifies a few objects out of the hundred or so in your program, all of the program's objects must be tested. For this reason the for statement can take longer that it seems it should, especially for a large program. If you want to find just the first instance of the set of objects you're looking for, you can use the break statement to jump out the instant is evaluated: # player needs a container in their possession { useful := UNDEFINED for each.IsAcontainer and each.location = player do { useful := each break } if useful then write "You can use ", useful.desc, "." else write "You don't have anything useful." } create named Most of the objects in your program will probably be defined within the program themselves. However, there are some situations where you would like to add an object to your program dynamically, that is, after the program is already underway. The create statement is the way to do this. This statement instantiates an object of and causes to point to it. is said to reference the new object. The new object will not have its own name; it is actually a nameless object. If you assign some other value to , without keeping track of the new object some other way, it will be hard to find again. (The for statement will pick it up, however.) Here is one application of the create statement. The following types define a list. The nodes act like "coathangers"; the senders of the 'Attach Me' messages are pointed to by the nodes, which in turn point to the next node down the list. In order for this to work, the nodes have to be created as they are needed. type node based on null next : UNDEFINED refer : UNDEFINED end type list based on null head : UNDEFINED temp : UNDEFINED methods 'Attach Me' : { create node named temp temp.refer := sender temp.next := head head := temp } 'Display List' : { temp := head while temp do { 'Display Self' -> temp.refer temp := temp.next } } end Note that the example above assumes that the senders of the 'Attach Me' message have a 'Display Self' method which they can respond to. destroy This statement performs the opposite of the create statement. If is pointing to a dynamically instantiated object, the object will be destroyed. Any other attributes which happen to be referencing it will now evaluate to UNDEFINED. We can extend the list type defined above by adding the following method to the list type: 'Shrink List' : { if head then { temp := head head := head.next destroy temp } This method shrinks the list by one element by removing the top element. Keywords Archetype has a number of keywords which cannot be used as object, type, or attribute names because they evaluate to special values depending on their context. each UNDEFINED outside of a for loop. Within a for loop, evaluates to each object in the program, starting with the first one defined for the first iteration of the loop and evaluating to the next one for the next iteration, and so forth. key Evaluates to a single keypress. When the program tries to evaluate key, execution stops until the user presses a single key. key will then evaluate to this character. message The last message received by the object. Most useful in a default method, where you may not know at the time of writing the program which message might be invoking the method. read Like key, except that program execution will be suspended until the user terminates their input with a RETURN. read will then evaluate to the entire line that the user typed. self Evaluates to the current object. Most useful in a type definition, where you are trying to code for the general case. sender Evaluates to the sender of message. Since Archetype does not support arguments, this is a method's only link to any necessary data that the sender might have. Operators and Expressions There are exactly three forms of an expression, which can be represented by the following BNF: :== ( | ( ) | ( ) ) The operators following are discussed in rough order of their importance and significance. Assignment ( :=, +:=, -:=, *:=, /:=, &:= ) The assignment operator always has the form := . If the left side does not evaluate to an existing attribute, a warning will be given. If it does, then the value of is placed into . The former value of is destroyed. The assignment operator has several cumulative forms: +:=, -:=, *:=, /:=, and &:= . These are all of the form := , and have the same effect as the statement := , but are shorter and faster. In other words, instead of a := a + 1 write a +:= 1 The value of the expression is always the final value of . Assignment operators group right to left, which is unusual (most Archetype operators group left to right); this is so that successive assignments can be made to the same value, so that: subj := dobj := verb := prep := UNDEFINED will set all four attributes to UNDEFINED. NOTE: it is important to remember that the attribute on the left hand side of the assignment will receive the value of the expression on the right, not its unevaluated form. What this means is that a statement such as abc := main.dobj will set the value of the attribute abc to the current value of main.dobj. If main.dobj changes its value later, abc's value will not change. This is in contrast to the practice of initializing an attribute with an expression, as described above in Object Declarations. Sending Messages ( ->, --> ) These operators are always of the form -> or -> , and simply send to . If has a method defined for , that method will be invoked, and the value of the expression will be the value of the last statement executed in the method. If is not a defined message, or is not a defined object, the value of the expression is UNDEFINED, and the message is never sent. Note that the vocabulary of sendable messages is built from all the single-quoted literals in your program. This simply means that you should always put messages in single quotes. If and are both valid, but does not have a method defined for , the value of the send expression is the word ABSENT. Note that this is not just a system-generated message; an object can actually return ABSENT if it wishes to inform the sender that it did not "handle" the message. This is most commonly used if an object had defined a default message, since the presence of such a method means that the object automatically "handles" any message given it. For example, the following default method handles direction messages, but not any others: default : if message -> compass then message -> handler else ABSENT The pass (-->) operator invokes the appropriate method from , but executes the method within the context of the current object, as though the current object were receiving the message. This is always the functionality used whenever the receiver is a , since types do not have attributes. One common reason to pass a message to a type is when you are defining an extension to an existing method in your type, and you do not wish to type (or do not know) all the previous code. When the player tries to drop the following object, there is first a condition: object superglue location : drawer desc : "super glue" sticky : TRUE methods 'drop' : if location = player and sticky then write "I can't! It's stuck to my fingers!" else message --> object # do what you normally do 'dissolve' : if sticky then { write "I dissolved the super glue." sticky := FALSE } else write "It's already dissolved." end Conditionals ( =, ~=, >, <, >=, <= ) These are operators that compare two values and return either TRUE or FALSE, if their operands are comparable, or UNDEFINED if not. They are used almost exclusively within if, while, and for statements, although they will always return their value no matter where they are used. If both operands are or can be converted to numbers, they are compared numerically. If not, then they are compared as strings, if possible. If they cannot be converted to strings, then the quantitative comparisons ( <, >, <=, >= ) will return UNDEFINED. The equal ( = ) and unequal ( ~= ) operators are special because they can compare non-numeric, non-string values to simply see if they are identical or not. Even though the operators ( <=, >= ) mean "less than or equal" and "greater than or equal" respectively, these cannot be used in all the same places because they are still checking quantitative equality. A few examples will make things clearer: Comparison Result 5 < 6 TRUE because five is less than six. "mosquito" < "moth" TRUE because "mosquito" comes before "moth" in the ASCII alphabet. 5 < "four" TRUE. Since "four" cannot be converted to a number (it is not composed of digits), 5 is converted to the string "5", and numbers always come before letters in the ASCII alphabet. 5 < "4" FALSE. "4" can be converted to the number 4; they are compared as such and 5 is greater than 4. "abc" = "abc" TRUE. "def" ~= "abc" TRUE. "def" is not "abc". "abcd" = "abc" FALSE. Two strings must be the same character for character in order to be considered equal. player.location = ballroom TRUE if, indeed, the location attribute of the player object points to the ballroom object. 7 <= 7 TRUE. player.location <= ballroom UNDEFINED. There is no universal sense in which object references can be measured. UNDEFINED < 7 UNDEFINED. UNDEFINED has no measurable value in any sense. UNDEFINED = 7 FALSE. 7 is indeed defined: as 7. box.wearing = UNDEFINED TRUE if either the wearing attribute of the box has not been defined or if it has been explicitly assigned the value of UNDEFINED. The Logical Operators ( and, or, not ) These operators always return values of TRUE and FALSE; they are used within if, while, and for statements (although they will return their values in other contexts as well) to take action based on a number of conditions: and will be TRUE if both its operands are neither FALSE, ABSENT, or UNDEFINED. or will be TRUE so long as at least one operand is neither FALSE, ABSENT, or UNDEFINED. not is unary; it returns TRUE if its operand is FALSE, ABSENT, or UNDEFINED, and FALSE if not. Because the precedences of these operators are all beneath those of the comparison operators, you can form natural expressions without parentheses such as: if player.location = trapdoor_room and trapdoor.is_closed then If you ever get confused, however, use parentheses to ensure correctness. They will not take any more memory at run time. However, familiarity with the operators' precedence is best of all, and the fewer parentheses, the more readable your code. The Arithmetic Operators ( +, -, *, /, ^ ) These perform simple arithmetic on numbers or values that can be converted to numbers, returning the result. They are evaluated as follows: ^ Exponentiation. Raises its left operand to the power of its right. Performed right to left. *, / Multiplication and division. Performed left to right. +, - Addition and subtraction. Performed left to right. This is the normal algebraic precedence of operators. Thus: 3 + 5 * 2 ^ 2 = 23 If you intend another precedence, simply use parentheses: ((3 + 5) * 2)^2 = 256 The String Operators ( &, within, length, leftfrom, rightfrom ) These operators manipulate strings. They are defined as follows: & Concatenation. Both operands are converted to strings; value is UNDEFINED if this is not possible. Returns a new string formed by concatenating the right operand to the left operand. "the " & "cupboard" becomes "the cupboard". within Search. Both operands are converted to strings; value is UNDEFINED if this is not possible. Returns an integer representing the position in the second string where the first string can be found. "boar" within "cupboard" = 4. If the first string cannot be found in the second, the value is 0. "lard" within "cupboard" = 0. length A unary operator expecting a single string (UNDEFINED if otherwise). Returns the number of characters in the string. length "snake" = 5, length "" = 0, length player.location = UNDEFINED (unless player.location is, in fact, a string). leftfrom Its left operand is a string; its right operand is the position in the string to take characters to the "left from". Examples: "cupboard" leftfrom 4 = "cupb", "cupboard" leftfrom 10 = "cupboard", and so forth. rightfrom Its left operand is a string; its right operand is the position in the string to take characters to the "right from". Examples: "cupboard" rightfrom 4 = "board", "cupboard" rightfrom 1 = "cupboard", and so forth. The leftfrom and rightfrom operators are a little clumsy; character range specification would certainly be clearer. However, substring operations are not all that common in adventure game applications. The real reason is that the design I chose for my expression parser couldn't handle range specifications. Someday I'll fix that. The Conversion Operators ( chs, numeric, string ) These operators are unary and return some transformation of their operand. They are UNDEFINED if their operand cannot be thus converted. chs CHange Sign. For numbers only; inverts the sign. Its alias is a minus sign ( - ) in front of a number. In other words, (5 * -3) and (5 * chs 3) are equivalent internally. numeric Converts a string explicitly to a number. Its alias is a plus sign ( + ) in front of the string. In other words, (numeric "453") and (+"453") are equivalent internally. string Converts its operand to a string if possible. TRUE becomes "TRUE", 364 becomes "364" and so forth. Its alias is an ampersand ( & ) in front of the value to convert. In other words, (string 453) and (&453) are equivalent internally. Because all Archetype operators attempt to convert their operands to the necessary type anyway, these operators are largely unnecessary except to test to see if the conversion is possible, as in if numeric obj.attr then ... The Random Operator ( ? ) This operator is unary and simply returns a random number in the range 1 - . To simulate two six-sided dice rolling, for example, you might write diceroll := ?6 + ?6 but not diceroll = ?6 * 2 which would have the effect of rolling a single die and then doubling its value. The Dot Operator ( . ) This ubiquitous operator expects an object reference on its left and an attribute name on its right. If the object has defined or inherited an attribute by that name, it returns the value of that attribute. If not, it returns UNDEFINED. The dot operator groups left to right (like most operators) so that "player.location.location.open" refers to the "open" attribute of the object pointed to by the "location" attribute of the object pointed to by the "location" attribute of the "player" object. In other words, only the very leftmost operand in such a string will be an object reference; the rest must be attribute names. The "Quote Me" Operator ( >> ) This operator, like a comment, continues from where it is used up to the end of the line in the source code. Instead of commenting out what follows it, however, it writes it to screen, just like the write statement. For blocks of text that need a particular kind of indentation, it is more convenient and readable than write because "what you see is what you get". Also, since the "quote me" looks only for the end of the line, any characters can follow it, including quotation marks and space, without prematurely ending the expression. Like the write statements, output from the "quote me" operator is also "paged" (paused for the user every 23 lines). Lines exceeding 75 characters or so will also be word-wrapped. Precedence of Operators Any expression enclosed in parentheses is evaluated first, beginning with the innermost set of parentheses. Where parentheses are not used, operators with the highest precedence are evaluated first. Operator Precedence Grouping . 13 left to right chs 12 unary numeric 12 unary string 12 unary random 12 unary length 12 unary ^ 11 right to left * 10 left to right / 10 left to right + 9 left to right - 9 left to right & 9 left to right within 8 left to right leftfrom 7 left to right rightfrom 7 left to right -> 6 left to right --> 6 left to right = 5 left to right ~= 5 left to right > 5 left to right < 5 left to right >= 5 left to right <= 5 left to right not 4 - and 3 left to right or 2 left to right *:= 1 right to left /:= 1 right to left +:= 1 right to left -:= 1 right to left &:= 1 right to left := 1 right to left Data Types Strings Strings are represented within double quote marks ("). If the string is supposed to contain a double quote mark itself, precede the double quote with a backslash. Doing so indicates that the double quote is to be part of the string, not the end of it. For example, write "My name is \"Bob\"" produces My name is "Bob" Two backslashes in a row (\\) become a single backslash within a string. There are four other characters that, when preceded by a backslash, produce a "special character" within a string: \b The backspace character \e The escape character (CHR(27)) \t The tab character \n The newline (a carriage return/line feed) However, be careful of using these characters within your Archetype strings because as of the writing of this manual, the word-wrap and paging algorithms do not recognize them! In other words, if you have a very long piece of text containing \n's, the output will not be stopped at the 23rd line. The output engine does not realize that the \n finished a line. Any other character preceded by a backslash becomes simply that character without the backslash. \m becomes m, for example. Also note that no string can ever exceed 256 characters is length. This unfortunate limitation is a consequence of the fact that Archetype was programmed in Turbo Pascal, which also has this limitation. (And the fact that the programmer was too lazy to define his own string type.) Numbers Archetype supports four-byte integers only. No real numbers, no floating-point arithmetic. Numbers therefore have the range of -2^32 to 2^32 - 1. Messages A message is enclosed in single quotes as opposed to a string, which is enclosed in double quotes. Any message can be converted to a string, but a string can only be converted to a message if the message appears somewhere else in your program in single quotes. The reason for this is that messages are stored in the .ACX file as a single number. This improves speed (and memory usage) a great deal and encourages the use of a message as a unique constant instead of as a free-form string. Therefore you do not have to worry that a long message such as 'DEBUG EXPRESSIONS' will be slower to send and receive than 'AB'; it won't. It is an Archetype convention to use all caps in a system message and all lowercase if the message represents a word from the parser. If you define your own messages for simple inter-object communication, the convention is to capitalize the first letter, as in 'Repeated Action'. Objects These are declared in the source code or created with the create statement. They cannot be converted to and from any other data type. They are valid on the right hand sides of the ->, -->, and := operators, and on the left hand side of the dot (.) operator. Types These are only declared within the source code; they cannot be dynamically created. There is little you can do at run-time with a type object; you are not allowed to reference its attributes with the dot (.) operator, and you cannot send messages to it with -> . You can, however, pass messages to it with -->. In fact if you use -> with a type on the right hand side it will be demoted to -->. This can be useful when an object has redefined a method but needs to invoke the original. Keywords Any identifier not declared as an object, type, or attribute is a keyword; a state value. These can be assigned and compared against but have no meaningful conversion to strings or numbers. If the /K option is used with the CREATE program, all keywords must be declared with the keyword statement. This helps catch spelling errors that might otherwise be very difficult to debug, particularly in a large program. The System Object As mentioned earlier, there is a special object name, "main", to which Archetype sends the message 'START' when the interpreter starts up. The second special object name in Archetype is "system", and refers to the system object that is part of the interpreter. This object performs parsing and parsing-related operations, sorting of lists of strings, debugging, and save/restore of the state of the interpreter. The system object is also different in one major respect from all other objects in Archetype: it is the only object that can receive free-form strings as messages. As mentioned before, all other Archetype objects can only receive strings which appear elsewhere as messages. Generally, the system object is sent messages which put it into one state or another, and then all strings it receives until another special string are considered data. Messages Pertaining to Parsing 'ABBR' Sets the abbreviation level of the parsing. This puts the system object into a state where it prepares to receive one numeric message; then it returns to idle. Set the abbreviation as follows: 'ABBR' -> system 4 -> system This would have the effect of turning "calculator" into "calc", both when the vocabulary is built and when player commands are being parsed. 'INIT PARSER' Initializes the parser and then puts the system into its vocabulary building state. See 'OPEN PARSER'. 'OPEN PARSER' Puts the system into vocabularly building state. Does not wipe out the vocabularly already built. In this state, all messages are assumed to be names of verbs or nouns, except the special messages 'VERB LIST', 'NOUN LIST', or 'CLOSE PARSER'. The object which the name refers to is assumed to be the sender. Multiple names for a single object can be separated by vertical bars; thus, the message 'rock|stone|rowlirag' is considered to be three synonymous names for the sender of the message. 'VERB LIST' All subsequent messages are considered to be verb names or synonyms. 'NOUN LIST' All subsequent messages are considered to be nouns. Either 'VERB LIST' or 'NOUN LIST' must be sent to the system after an 'OPEN PARSER'; there is no default. 'CLOSE PARSER' Stop receiving vocabulary items; return to idle state. 'ROLL CALL' To help it resolve ambiguities, the system needs to know which objects are "nearby" the player. In other words, if there are six different objects defined in a game which are all called "button", and the player types "push button", the button indicated is probably the one nearby. 'ROLL CALL' erases the current sense of what is nearby and prepares the system to receive 'PRESENT' messages. 'PRESENT' The sender is assumed to be a "nearby" object, and will continue to be considered such until the next 'ROLL CALL' message. 'PLAYER CMD' Like 'ABBR', this takes a single "argument": it puts the system into a state where the next message received is considered data, then returns to idle. In this case the message is the exact string the player typed in. 'NORMALIZE' Returns the normalized player command as of the last 'PLAYER CMD' message. This string will be in all lowercase, with words separated by a single space, all punctuation removed except apostrophes and hyphens, and will always have one trailing space. If the player typed nothing at all, 'NORMALIZE' will return a single space. 'PARSE' Parses the last string received through 'PLAYER CMD'. Does not return anything, simply performs the action of parsing. Gets rid of all instances of the words "a", "an", or "the" before parsing. 'NEXT OBJECT' This is how the system object indicates which objects matched what words in the player's string. The string from 'PLAYER CMD' will have been turned into a sequence of objects. This is possible because even verbs have objects associated with them. 'NEXT OBJECT' returns the next object in the list, from left to right. If the word or phrase didn't translate, 'NEXT OBJECT' returns the string that failed to parse. When the list of objects is exhausted, 'NEXT OBJECT' returns UNDEFINED, and will do so until the 'PARSE' message is sent again. 'WHICH OBJECT' Used to look up an object, given its parse name. Like the 'PLAYER CMD' message, this message puts the system into a state where it expects that the very next message received will be a string with the name of an object. When that name is received, the system goes back to idle state and returns a pointer to the object with that name, or UNDEFINED if no such object exists. The lookup is based on the vocabulary given to the system since the last 'CLOSE PARSER' message. Messages Pertaining to Sorting 'INIT SORTER' Initializes the system object's sorting algorithm, which is the heap sort. It then opens the sorter, as if an 'OPEN SORTER' message was sent. 'OPEN SORTER' Prepares the system object to receive data. Every string sent to the system is dropped on the heap as sortable data from now on until the message 'CLOSE SORTER'. 'CLOSE SORTER' Stop receiving sort data; return to idle state. 'NEXT SORTED' Returns the next string in a sorted list. Sorting is always done in ascending order, so the string returned will have been the lexically "smallest" string since the last 'CLOSE SORTER' message. After returning the string, it is removed from the heap, and when the heap is empty, 'NEXT SORTED' returns UNDEFINED. Messages Pertaining to Debugging These three message are all "toggles": sending the message turns the associated property on; sending them again turns it off. For these messages to be useful at all, the Archetype code must have been CREATEd with debugging information. 'DEBUG MESSAGES' Prints out to the screen every message that is sent; who it's being sent to and who is sending it. 'DEBUG EXPRESSIONS' Prints to the screen every expression that is going to be evaluated and what it evaluates to. 'DEBUG STATEMENTS' Prints to the screen every statement that is going to be executed. The following messages are not toggles; they are simple actions that only report the state of the interpreter. 'DEBUG MEMORY' Outputs a short report on the number of bytes free and what the maximum memory request is allowed to be. 'FREE MEMORY' This is a query, not a command. Returns an integer which is the number of bytes free for Archetype to use. Messages Pertaining to the Interpreter State Both of these messages behave like 'WHICH OBJECT', above: send the message to the system and follow it with the name of the file to use as the state file. Both messages return UNDEFINED if the file cannot be opened. 'SAVE STATE' Saves the state of the interpreter to the given file name. This includes the values of all attributes and any dynamically created objects. It does not include the assembled vocabulary. Returns TRUE when done. 'LOAD STATE' Loads all of the information in the state file with the given file name. It will not load, however, if the state file was not written by the exact same .ACX file. In other words, if you write out a state file from within a program, and then recompile that Archetype program, the new .ACX file will not be able to load the previous state file. If this happens, the message will return FALSE; otherwise it will load the information and return TRUE. Appendix A Backus-Naur Form The Backus-Naur Form (or BNF) is a way of representing syntax. In this manual, the meaning of various symbols is as follows: boldface - the use of boldface type means that this should be typed literally. - something in is symbolic; it refers to something else. ( ) - parentheses mean that everything inside is to be considered one thing. | - the vertical bar is used to separate alternatives. For example, (a | b | c) means exactly one of a, b, or c. (a (b | c)) means ab or ac. [ ] - square brackets mean that what is inside is optional; you can use it or not. (a [b | c]) means either a, ab, or ac. * - the asterisk means "zero or more" of what it follows. ab* means a, ab, abb, or abbb, and so on. Example: The following BNF: ( write | writes | stop ) [ (, )* ] can be expressed in English as: Either the word write, writes, or stop, optionally followed by an expression and zero or more instances of a comma followed by another expression. Some of the valid statements you can construct (leaving within angle brackets) from this BNF are: write stop writes , write , , stop