The U Programming Language Introduction. The U programming language (hereafter referred to as simply U) is a simple language reminiscent of C or awk. This document is intended as a 'quick start' guide, rather than a formal language definition, since it is expected that the language will be constantly changing and evolving. 1. Hello World The primary means of sending text output to a user's terminal in U is via the echo command and its relative echoto. Echo echoes its arguments back to the caller, per- forming type conversion on numeric values, etc. C-like escape sequences are recognized: echo("hello world!\n"); will print "hello world!" followed by a newline. Unlike C, there is no need for a main() function to call. Commands can be executed inside or outside of a function. echo("two plus two = ",2 + 2); will print "two plus two = 4", with echo converting the numeric value generated by the arithmetic expression into a text string automatically. echo places no reasonable limit on the number of parameters that can be passed to it in one call; everything is printed in order, left to right. 2. Variables 2.1. Variable Types and Data Types Variables in U can be of several types. The interpreter hides the details of the types from the user to the greatest possible extent. Currently, the existing types are as fol- lows: o+ Numbers - the basic numeric type in U is the integer. In fact, there is no provision for floating point numbers at all. Typical numerical operations such as division, multi- plication, addition &c can be performed on numbers. o+ Strings - the string type in U is a simple chunk of text, June 22, 1990 U Programming Language with the interpreter handling storage allocation (unlike C) for the programmer. Numeric operations are NOT permitted on strings, with the exception of the equality operator. o+ NULL - the data value NULL in U is a special data type of its own. Unlike C, it does not mean zero - it means nonex- istence. NULL is provided for many reasons, primarily being error checking, and to provide a fixed 'magic' value that programmers can use. Many of U's internal operators return NULL in the event of an error, or invalid argument. In real- ity there is an error value that can be attached to NULL, and accessed by the user, allowing a user to determine exactly why their operation resulted in a NULL. Examples of operations that can result in NULL are: calling a non- function as if it were a function, attempting to access a non-existent variable, and attempting to use a function parameter that was not passed to a function. o+ Objects - an object in U is a numbered entity to which smaller variables (called elements) can be bound. It is the basic "star stuff" of a Ubermud universe. Objects are always referred to by number, preceded with a sharp, or '#' symbol. There is a special object - the system object, which can be abbreviated with the '@' symbol. This is always object #0, but using the '@' syntax is preferred, since it is clearer. o+ Lists - a list is an array of object numbers. There are functions that manipulate, search, add to, and delete from lists. The list is used heavily in implementing a universe, since lists contain contents of rooms, player inventories, and so forth. There is no heterogenous array type in U (due to the complexity of implementing it), so lists may only consist of object numbers. This is less of a drawback than it may seem, since arrays of strings can easily be gotten at indirectly through the object number. o+ Functions - a function or procedure is actually a primary data type in U. This permits assigning of functions to vari- ables, and calling the function through that variable. This can lead to confusing or very powerful code, depending on how wisely it is used. There are some restrictions on the use of functions and their declaration, that are discussed later. Functions can be passed as parameters to other func- tions, assigned to temporary variables, or assigned to object elements, which are described in greater detail later. 2.2. Temporary Variables In U, a temporary variable can be used to store vola- tile information. The temporary disappears after the func- tion that it is in is terminated. The advantages of tem- poraries is that they are quick and allocation and dealloca- tion of them is handled by the system. Due to their June 22, 1990 Copyright, Marcus J. Ranum, 1990 ephemeral nature, however, they cannot be used outside of functions, without disappearing immediately. Temporaries CAN be made to last outside of functions by enclosing the desired block of code in curly braces: { $tmp = strtime(); echo("$tmp is:",$tmp,"\n"); } Here $tmp is set to whatever the function strtime returns (a string form of the system clock), and is later handed to echo for printing. 2.3. Function Parameters In U, a function can be passed an arbitrary number of parameters. Unlike C, where the parameters are named, the parameters in a U function are accessed with $-number syn- tax, similar to the Bourne shell's. A second function param- eter variable $# contains the parameter count (zero being none). This allows a function to access its parameters without having to name or type variables: func @.foo { if($# > 0) echo("the first parameter is \"",$1,"\"\n"); } @.foo("bar!"); Here a function is called with one parameter. A check against the parameter count is made, and if there is a parameter, the first one is printed. The example above pro- duces the following output: the first parameter is "bar!" note the escaped quotes preceded by the '\' character in the parameters to echo. It is forbidden to use the parameter variables outside a function; attempting to do so will abort compilation of the program at that point. 2.4. Elements The element is the most important type of variable in U, and will be gone into in greater depth later. Elements are formed of an object number, and the name of a variable stored in that object. An object can have any number of elements stored in it, provided they are all uniquely named. Some examples of elements are: June 22, 1990 U Programming Language #44.foo; @.fuzzy; #55.this.that; The @.fuzzy syntax is an example of using '@' to specify the system object. It could be written as #0.fuzzy as well. In the case of #55.this.that, there is a level of indirection added. When the value is presented to the interpreter, the interpreter will first check the contents of #55.this. If #55.this is itself an object number, it will then search the contents of that object for variable name that. This can be done to any number of levels, though there will not likely be need for more than one or two levels of indirection. If a variable in an indirection is not an object number, or does not exist, then a "badly formed object specifier" error is generated. For example: #55.foo = 666; #44.bar = #55; echo("indirecting:",#44.bar.foo,"\n"); Will print: indirecting:666 Since the object number #55 is stored in #44.bar, which is looked up first, reducing #44.bar.foo to #55.foo, which is in turn looked up, giving the number 666. An element can be a value of the type of any of the data types permitted in U. Elements are treated essentially as variables in expressions: #44.foo = 12; $tmp = 2; #55.bar = #44.foo * $tmp; But they can just as easily be treated as functions: #33.bar("this is a test\n"); $tmp = #33.bar; $tmp("this is a test\n"); In both of the examples above, the function stored in #33.bar is called with the message: "this is a test\n". 2.5. Accessing Elements Via Strings Elements can be accessed by coercing a string into the name of an object's element. This is done by providing an arbitrary expression which returns a string in the object element specifier: June 22, 1990 Copyright, Marcus J. Ranum, 1990 #33.("foo") = 55; The program above would convert the string value "foo" into an element name and search #33 for it. Parenthesized expres- sions can be mixed at will with other element names: #33.foo.("bar") = 55; $array = 1; echo(#33.(str("foo",$array))); In the examples above, #33.foo.bar would be set to 55, and in the second #33.foo1 would be echoed. Note that there are some restrictions on string to variable coercion. The max- imum length of an element name is less than the maximum length of a string, and attempts to coerce a name that is too long will automatically set the value to NULL. If the expression fails to return a string, the element name forma- tion fails, and the value is set to NULL. While string-to-variable coercion is a powerful func- tion, it is also an indicator of a failing in the design of the language that encompasses it. In the case of U, string-to-variable coercion is incorporated to get around the lack of support for heterogenous arrays. Programmers are urged to use string-to-variable coercion sparingly, if at all, and primarly to use it as a means of examining things from the command line. 2.6. Making Elements Go Away Elements attached to an object can be de-assigned (annihilated) by assigning them to NULL. This frees their storage in the database, and removes them from the index. Objects themselves cannot be destroyed in this simple manner. 3. Functions 3.1. Basics of Functions A U function is similar to a C function in that it can take a list of parameters of varying types, and return a single value. Unlike C, U does not have a notion of pointers as parameters, so it is difficult for a U function to modify something passed as a parameter. Furthermore, unlike C, U functions cannot modify the values of their numbered parame- ters. Assignments to $1-N or $# are syntax errors, and abort compilation. Functions that do not explicitly return a value return NULL(function returned no value) to the caller, if the value of the function is taken. Functions can return any legitimate U data type, including other functions. June 22, 1990 U Programming Language 3.2. Declaring a Function A function is declared in the form of: func #object-number . name { statements; } The object-number may be '@', if the function is to be attached to the system object, but otherwise it must be the number of an existing object that the programmer has permis- sion to write to. The curly braces are optional, and may be omitted if the function is a single statement. Take this simple function: func #33.days { $tmp = $1 * 24 * 60 * 60; echo("in ",$1," days, it will be ",strtime($tmp),"\n"); } which prints what it will be in a number of days passed to the function as a parameter. Obviously, the function should check for the existence of a parameter, since $1 will be NULL if it was called with no parameters. 3.3. Returning From a Function In the example of #33.days above, the function "drops off the end" of the code. This causes the function to return to the caller, and leaves the value NULL for the calling function to access, should it so desire. A specific value can be returned to the caller using the return operator, or NULL can be specifically returned to indicate an error. To simplify this instance, the return operator can be used without a value, implicitly returning NULL. func #33.bad { if($# != 3) { echo("three arguments expected!\n"); return; } return($1 + $2 + $3); } echo(#33.bad(4,5),"\n"); echo(#33.bad(4,5,6),"\n"); This overly simplistic example checks its argument count, and returns its parameters added together. In the first case where it is called above with two parameters, the error June 22, 1990 Copyright, Marcus J. Ranum, 1990 message will be printed, and NULL will be returned. echo quietly refuses to print NULL (since NULL does not exist), so there would be be a newline printed, but no other output than the error message. In the second call, the function returns 15, which is passed to echo, and is printed, with the newline. 4. Conditionals U's conditionals are the if and else syntax so familiar to C programmers. Conditionals take the form of: if ( expression ) statements; if ( expression ) statements1; else statements2; The expression can be any legal U expression. If the expres- sion evaluates to a non-empty string, or a non-zero numeric value, the statement following is executed. In the if-else syntax, the second set of statements are executed in the case that the condition of the first failed. U has the C- like boolean operators '&&' and '||', and and or, respec- tively. As in C, if statements can be cascaded and nested. One crucial difference exists between C and U if state- ments: unlike in C, all the clauses in the conditional are evaluated (C stops when the condition is manifestly true or false). Thus: if(foo() && bar()) { ... } will evaluate foo() and bar() both, while in C, bar() will never be called if foo() returns 0. 5. For Statements 5.1. Problems With For Statements, While Loops, and Recur- sion U does not have C-like for and while statments, since they permit a programmer to write code that loops eternally. This is intolerable in a multi-user game, and, in fact, they turn out to be unnecessary. Another problem is recursive functions, or functions that call each other endlessly back and forth. The U interpreter handles these simply by permit- ting recursion until the reasonably large internal stack is overflowed, and then returning NULL when any further func- tion calls are attempted. This effectively halts the June 22, 1990 U Programming Language recursion, causing it to "unwind" itself normally. Obvi- ously, a function that recursively generates a huge amount of text output will still be a tremendous annoyance, but controlling such things cannot be done within the software. 5.2. Syntax of For Statements Since a MUD deals primarily with lists of things, U has syntax for iterating across lists, and across the parameters to a function. The syntax of for loops takes two forms: for $tmp in ( expression-returning a list) statements; foreacharg $tmp statements; In the first case, the temporary variable $tmp is succes- sively set to each element number for every element on the list. If the expression fails to return a list, or returns an empty list, the statements are not executed at all. In the foreacharg loop, the value of $tmp is successively set to each parameter that the function was called with. foreacharg cannot be used outside of a function, as this is a syntax error. In the following example: #11.str = "string 1"; #22.str = "string 2"; #33.str = "string 3"; #33.list = listnew(#11,#22,#33); foreach $tmp in (#33.list) { echo($tmp,".str is:",$tmp.str); } This creates a list containing the object numbers #11, #22, and #33, and iterates across the list, printing (succes- sively) the values of #11.str, #22.str, and #33.str. In the next example: func #33.bar { $cnt = 0; foreacharg $tmp { $cnt = $cnt + 1; echo("parameter ",$cnt,"=",$tmp,"\n"); } echo("total parameters:",$cnt,"\n"); } #33.bar(4,"foo",strtime()); The following is printed: June 22, 1990 Copyright, Marcus J. Ranum, 1990 parameter 1=4 parameter 2=foo parameter 3=Fri May 11 16:24:21 EDT 1990 total parameters:3 6. References 6.1. Generating References U stores all objects and object elements except for those in the system table (#0) in a database with an index. A reference is used in U to make two or more index entries that "point" at the same basic object. This allows objects that wish to share the same element values to do it easily, and provides performance advantages as well as saving storage space. Additionally, references are used in some internal built-in functions to manipulate index entries for permissions and ownership purposes. A reference to an object element is generated by placing and ampersand '&' character before the name of the element. EG: !.foo; &$tmp.foo; ,.bar.baz; &@.froz; All of these are examples of legal references. It is not legal syntax to take a reference of anything but an object element. IE: ! &$tmp; &@; are all illegal references, producing syntax errors at compile-time. 6.2. Assigning to References When an assignment is made to a reference, instead of saving a copy of the data, a pointer to the copy of the data is saved. It must be emphasized that this is NOT a perfor- mance expensive operation, rather the contrary. For all intents and purposes, a referenced object is exactly the same as any other stored object. When an assignment is made to a referenced element, the contents of the reference are NOT overwritten, the reference is broken, and new storage is allocated for the assigned element. For example: #33.foo = "foo!"; #44.foop = !.foo; echo(#33.foo,#44.foop,"\n"); June 22, 1990 U Programming Language #44.foop = "bar!"; echo(#33.foo,#44.foop,"\n"); The first time echo prints: "foo!foo!" followed by a new- line. The second call to echo prints "foo!bar!" followed by a newline. In certain cases it may be desirable to change to contents of a reference. This can be done by prefixing the assignment operator with an asterisk. This causes the con- tents of the reference to be set, changing the value for every element that "points" to it. #33.foo = "foo!"; #44.foop = !.foo; echo(#33.foo,#44.foop,"\n"); *#44.foop = "bar!"; echo(#33.foo,#44.foop,"\n"); As in the previous example, the first call to echo prints "foo!foo!" and the newline. The second prints "bar!bar!" and the newline. This can be invaluable for implementing objects that change their appearance globally, but a programmer does not want to have to maintain individually, such as a desti- nation list for a teleporter system. 6.3. Restrictions on References It is illegal to make a reference to object elements in #0, the system object. This is because the entire system object is stored only in main memory, not on disk (it is the repository of that which must be fast). Thus, a reference to something in #0 would become "stale" as soon as the server was shut down. Assigning the contents of a reference to NULL is a special case, and causes only that one link to the referenced object to be broken. This is due to the fact that there is no idea how many object elements actually "point" to that referenced element. 7. Run-Time User Contexts 7.1. User-Id and Effective User-Id When a U program is executing, there are several spe- cial values attached to the running program. Primarily these consist of the user-id and the effective user-id. These values are used as the basis for all permissions in U. At any time when a player is connected to the U-server, their login has a user-id associated with it. Typically this will be the object number of their 'self' in the game (though it need not be). The effective user-id is almost always the same as the user-id, but it can be changed under certain circumstances (see Setuid Function below) or if the person's connection is flagged with a "Wizard Bit" in the password file. In the case of a connection with the "Wizard Bit" set, June 22, 1990 Copyright, Marcus J. Ranum, 1990 the players user-id will be their object number, but their effective user-id will be that of #0, the Creator, who has absolute power over all things in the universe. At runtime, built-in functions may also alter a connections effective user-id, though it is impossible to alter the user-id, and for good reason, since the user-id determines where any tex- tual output intended for a player should go. Should two objects somehow share the same user-id, they would both get each other's output. 7.2. Selves and Actors At run-time, certain special object number variables are defined, and can be used in functions to reference the player who triggered the current function calls, or the object number of the current function's root object. These values are #actor, #caller and #self. When a function uses #actor as an object number, it is replaced with the object number of the player whose input is currently being pro- cessed. This is very, very useful: func @.take { ... foreach $who in (#actor.location.players) echoto($who,#actor.name," picks up ",$thing.name,"\n"); ... } Suppose the object number of the person who typed the 'take' command in were #44. This function would iterate across the list of players in #44.location.players, echoing the player's name, and so forth to them. This permits functions to be written in a manner that is fairly independent of who or what calls them. The #self special object identifier is similar, but identifies the object number of the current function's root object. This allows a function to modify its proper root object. Suppose we have a radio that we wish to have alter its description depending on whether or not it is turned on. We might write: func #44.turnon { ... if(#self.on == NULL) { #self.on = 1; #self.description = "a blaring ghetto-blaster..."; } ... } Granted, #44 could be substituted for #self in this example, but using #self makes it easier to make programs that are object number independent. Another crucial facet of #self June 22, 1990 U Programming Language arises when a function that is called is acually a function called with a reference. In such a case, using #44 would set the wrong value in the wrong radio depending on which radio's function were called. Using #self guarantees that the correct root object number is applied. Using the func- tion above: #55.turnon = ,.turnon; #55.turnon(); #44.turnon(); The first call, to #55.turnon() would modify #55.on, and #55.description since #self would be object number #55. In the second call, the value of #self would be correctly set to #44. The #caller special object variable is replaced at run-time with the object number of the object that has invoked the current function. If the current function has been called directly from a player, or the monitor, #caller is identical to #actor. func #33.bar { echo("self is ",#self,"\tcaller is ",#caller,"\n"); } func #44.bar { echo("self is ",#self,"\tcaller is ",#caller,"\n"); #33.bar(); } #44.bar(); In the example above, there are two functions, one of which calls the other, and is in turn called from the monitor directly. If this code were run with the user-id #55, the results would look like: self is #44 caller is #55 self is #33 caller is #44 Since the first call to #44.bar was executed directly by object #55, the #caller value is exactly the same as #actor. 8. Permissions In U, all objects have a variety of permissions associ- ated with them. Each object has an owner, and a masked set of permissions flags, somewhat reminiscent of the permis- sions bits on a UNIX file. Permissions can be granted for read or write. The concept of execute permission is meaning- less, since functions are a primary data type, and being June 22, 1990 Copyright, Marcus J. Ranum, 1990 able to read them implies being able to execute them. There are three classes of permissions: owner permissions, indirect permissions, and world permissions. Owner permis- sions are checked when the owner of a on object or object element attempts to modify or access it. Indirect permis- sions are checked when a function that is running with the effective user-id of the owner, but a different real user-id tries to modify or access an object, and world permissions are checked in all other cases. The purpose of the indirect mode is to prevent objects from seriously damaging a player or his internal data, as a result of the player's action. There is no notion of 'groups' per se in U, since there are really no classes of objects to group things by. 8.1. Permissions on a Root Object When the system built-in function objectnew is called, it allocates a new empty object, and returns its object number. This empty object can have elements (data values) added to it as part of the usual assignment process described earlier. Whenever an assignment is made to an object element that does not already exist, the permissions on the root object are checked, and must be in a mode to permit writing before the element can be created. If an attempt is made to destroy the root object, a check is made to see if it is writeable. This makes it easy for an object to be created that can have new elements added to it by other than the player, by simply making the root object world-writeable. This also makes it feasible to create objects that can be destroyed by people other than the owner, which is sometimes desirable. Read permission on a root object is (currently) meaningless. 8.2. Permissions on an Object Element The same permissions bits exist for an object element as for a root object, but some of the effects of permissions bits are interpreted differently. If an assignment is made to an element, the existing element must be in a mode that permits writing by the caller, based on the effective user- id. This applies even if the assignment is to NULL, so it is possible to create an object element in someone else's root object that they cannot destroy. This is a feature rather than a bug, as it permits the universe rules designers to make some values outside the user's control. If, however, a root object is destroyed, all elements are destroyed, regardless of ownership. There is an additional feature to aid in designing universes: only the universe Creator can create an element which has a name beginning with an under- score '_' character. This is to reserve a set of names for universe-implementations, without forcing the Creator to always set values and permissions to "hold" the variable names. June 22, 1990 U Programming Language 8.3. Permissions on a Function - the Setuid Bit A special permissions mode can be applied to functions, which causes the function that is being executed to run with its effective user-id to be that of the owner of the func- tion. This provides a controlled means for giving access to objects without having to make the object world-writeable. Setuid functions owned by the Creator are especially useful for implementing universe rules, since the Creator may want to reserve some privileges for itseslf (such as moving objects in rooms). Setuid is a very powerful capability, but can also cause serious problems if not used with care. Any functions that are called by a setuid function inherit their effective user-id from the setuid function. Thus, the writer of a setuid function must be careful to control sub- functions that are called, since a joker might take advan- tage of permissions to destroy objects, or change their per- missions and ownerships. The setuid model of permissions will be immediately familiar to anyone who has more than a nodding acquaintance with UNIX. Such a user will also know that 95% of the secu- rity loopholes in UNIX are as a result of an incorrectly applied setuid program. An Ubermud Creator will want to exercise some caution, since a flaw in the universe rules will allow players to assume the powers of a wizard. On the other hand, Ubermud being a game, this can be fun - or at least less serious than having a multi-user computer broken into. The possibility of destructive Uber-virusses is not to be ignored, however. 8.4. Changing Permissions Permissions on object elements and root objects can be altered with the built-in chmod() function. Chmod can be given either the number of an object (to affect the root object's permissions), or a reference to an object element. Chmod takes a second parameter, which is a string specifying the permissions modes for the three types: Owner, Indirect, World. The types are signified with upper-case letters, fol- lowed by a colon and a list of lower-case letters indicating the desired mode for that type of permission. chmod(#33,"O:rw"); chmod(!.bar,"O:rw,I:r,W:r"); chmod(!.somefunc,"O:rw,I:r,W:rs"); The first example sets the permissions for the root object of #33 to be owner read and owner write. Since the permis- sions for indirect and world are not specified, no opera- tions are permitted to anyone but the owner. Since the object is a root object, the read permission does not mean much, but the lack of world or indirect write permission means that nobody but the owner can create new elements in June 22, 1990 Copyright, Marcus J. Ranum, 1990 that object. The second example shows a reference to #33.bar being passed to chmod, with a request to set that element only as owner readable and writeable, indirect and world readable. This is, in fact, the default permissions mode, which is automatically set when an object or element is ini- tially created or assigned to. The third example shows a function being marked as owner read and writeable, indirect and world readable, and setuid. To flag an element as setuid, the 's' must be marked within the world group of permissions, as a reminder to the programmer that world access is being given to the owner's permissions. 8.5. Changing Ownership Objects can be given away in U, as can individual object elements. Only the owner of an object or element (or the Creator) can give an object away. Giving the root object away does not automatically change the ownership of all the object elements in that object, nor does giving away a sin- gle object element give any other permissions within that object. The only change other than ownership that occurrs when an object is given away is that the setuid bit is cleared if it is set in the gift. This is to prevent the obvious security loophole. Calls to the built-in function that manages changing ownerships are done similarly to set- ting permissions. An object number, or a reference to an object element is given, and the object number of the reci- pient is provided as a second parameter: chown(#33,#44); chown(,.name,#44); The first example above shows a root object #33 being given to #44. Note that #44 does not have to be a player. In fact, the recipient does not even have to exist. Object elements within the system object (#0) can be given away by the Crea- tor if so desired, but the system object itself cannot be given away. Typically ownership changing is useful in creating universe rules, where a creator may wish to create a base object that will be a player, and then give it to itself. In some universes, or in some cases, the Creator may not want a player to own their base object. Such issues are outside of the scope of this document. 9. Built-In Functions U has a fairly powerful set of built-in functions, which can be called exactly like user-defined functions, and return (or do not) values as appropriate. For the purposes of documenting them, they are listed below, in a manner intended to be somewhat reminiscent of the manner in which C functions are usually declared, with the return value, and parameter types. Some U built-ins take arbitrary lists of parameters, some take specific parameters of given types. In June 22, 1990 U Programming Language the case of the latter, NULL is returned when the parameter count is incorrect, or the wrong data type is provided as a parameter. 9.1. General Functions NUM atoi(STR) - returns a numerical representation of the string, or zero. If a NUM or OBJ is passed to the function by accident or on purpose, a type conversion will be per- formed, and the corresponding NUM value will be returned. NULL is returned if more than one parameter is given, or the parameter is a function, list, or other type. OBJ atoobj(STR) - converts a string to an object number, with the same caveats and type conversions at atoi. NUM catfile(object#,STR) - sends the contents of the file named in the string parameter to the output buffer of the player object listed as the first parameter. Files are searched for in a subdirectory of the U-server's current directory named "files" for security reasons. If the file cannot be opened, the player is informed as to the reason. Files with a leading '/' character or a '..' in them are forbidden, and will not be printed. In the event of a failure, the function returns NULL; a successful call is indicated with a numeric zero return value. INT disconnect(object#1,object#2,object#N) - disconnects the specified object numbers from the server if they are con- nected. This function must be run with user-id or effective-user-id of Creator. Note that since this is a built-in command, the caller must generate their own call to @._quit(), or whatever is necessary to cleanly disconnect the player. NULL echo(arg1,arg2,argN) - echos its parameters to #actor in sequence, performing type conversion as necessary. NUM values are converted to a text format, strings are printed as such, and object numbers are printed preceeded with a sharp '#' character. NULL values are not printed, and attempts to print functions and lists prints either "" or "" respectively. NULL echoto(object#,arg1,arg2,argN) - echoes its parameters just like echo, except that the output it sent to the player matching object# if that player is connected. If the desti- nation object number is not connected, the output is thrown away. NUM errno(NULL) - returns a numeric representation of an error attached to a particular NULL value. Error values are: 0 - No error. 1 - Error. This is produced when a user program returns NULL. June 22, 1990 Copyright, Marcus J. Ranum, 1990 2 - Out of memory. This indicates a memory alloca- tion condition within the server. 3 - Numeric operation on non-number. This indi- cates an attempt was made to perform a numerical operation on a non-numeric value. The only numer- ical operator that is permitted on non-numbers is the equality ("==") operator, which functions between NULLs and strings. 4 - Division by zero. This indicates a numerical division by zero was attempted. 5 - Badly formed element specifier. This indicates an attempt was made to create an element out of something invalid. For example, trying to make an element out of a string value and an element name. (EG: $foo.bar where $foo = "foo"). 6 - Bad parameter type. A built-in function was called with an incorrect parameter type, or a missing or extra parameter. 7 - Nonexistent object. An attempt was made to access an object or object element that does not exist. In the event that an object is not readable by a person, this value is returned, not a permis- sion denied value. This is to make non-readable values completely and utterly "invisible". 8 - Cannot reference object. An attempt was made to take a reference of an object that cannot be referenced. This usually results from an attempt to take a reference of a badly formed element specifier. (EG: &$foo where $foo = "bar"). 9 - Function returned no value. An attempt to use a return code from a function that returned no value results in the return code being NULL, with this value. There are many cases where this is perfectly acceptable. 10 - No such parameter. An attempt to use a func- tion parameter that was not passed returns this NULL. (EG: $4 in a function that was only called with two parameters). 11 - I/O error (this is bad!). Something failed in the disk-based database routines. This is very, very, very bad. Run about in a panic. 12 - Permission denied. An attempt was made to operate on an object which has permissions set against the operation. 13 - Not owner. An attempt was made to change the ownership of an object or object element that the caller does not own. 14 - Stack over/underflow. Some form of recursive or looping call was made, resulting in an eventual failed function call. STR error(NULL) - returns a string representation of an error message attached to a particular NULL value. This is useful for debugging or explaining why an operation failed. June 22, 1990 U Programming Language $ret = #33.bar = "this is a test"; if($ret == NULL) echo("operation failed: ",error($ret),"\n"); The above example is a typical means of performing error- checking withing a U function. NUM islist(arg) - returns a numerical value of one if the parameter is a list. Numerical zero is returned otherwise. NUM isnum(arg) - returns a numerical value of one if the parameter is a number. Numerical zero is returned otherwise. NUM isobj(arg) - returns a numerical value of one if the parameter is an object number. Numerical zero is returned otherwise. NUM isstr(arg) - returns a numerical value of one if the parameter is a string. Numerical zero is returned otherwise. NUM rand(optional arg) - returns a random number from zero to the maximum numeric value allowed on the system. If a single numeric parameter is given, the range of the number returned will be between zero and the parameter. If a param- eter that is non-numeric is provided, numeric zero is always returned. echo("1d6=",rand(6) + 1,"\n"); The above would produce a random number as if rolled on a six-sided die. NUM regcmp(string1,string2) - returns a one or a zero, depending on whether or not string1 contains text matched by the regular expression in string2. If the regular expression in string2 is malformed, or a parameter is mis-matched, zero is returned. STR regexp(string1,string2) - returns a string consisting of the first completely matching substring in string1, using the regular expression in string2. If there is no match, a parameter is missing or mismatched, or the regular expres- sion is malformed, the function returns NULL. Regular expressions are similar to those used in egrep, and include the '|' operator. echo(regexp("this is a test","this[ ][a-z]")); In the example above, the string "this is" would be returned by regexp, and echoed. INT shutdown() - shuts the server down gracefully. This function must be run with user-id or effective-user-id of Creator. June 22, 1990 Copyright, Marcus J. Ranum, 1990 STR str(arg1,arg2,argN) - returns a string assembled from the parameters given. Conversion on the parameters by type is performed in the same manner as in echo. This function is very useful for producing concatenated values for assign- ments: #33.time = str("the time is:",strtime()); The above appends the returned value of strtime to the end of the string, and assigns it to #33.time. NUM strlen(STR) - returns the length of the string passed as a parameter. If the value is not given, or is not a string, numeric zero is returned. STR strtime(optional arg1) - returns a text string represen- tation of the system's current notion of the time. If a numeric parameter is provided, the time string returned is the notion of the time as if it were at the given number of seconds GMT since January 1, 1970. NUM time() - returns the system's notion of how many seconds it has been GMT since January 1, 1970. 9.2. List Manipulation Functions OBJLIST listadd(OBJLIST,object#1,object#2,object#N) OBJLIST listprepend(OBJLIST,object#1,object#2,object#N) - prepends the given object numbers to the list provided as a first parameter, and returns the new list. In the event of an parameter type mismatch in the first parameter, NULL is returned. If any of the object number parameters are incorrect, the new value is silently ignored. If a new value already exists in the old list, it is not prepended again, and retains its old position in the list. These two func- tions are actually the same, under different names, listadd being included for brevity's sake. OBJLIST listappend(OBJLIST,object#1,object#2,object#N) - appends the given object numbers, in the same manner as lis- tappend. NUM listcount(OBJLIST) - returns a numerical count of the number of element numbers in the list provided. In the event that parameter one is missing, or is not a list, NULL is returned. OBJLIST listdrop(OBJLIST,object#1,object#2,object#N) - returns a new list, consisting of the object numbers from the list in the first parameter, with any occurrences of the object numbers given as parameters omitted. In the case of a call to listdrop completely emptying a list, an empty list is returned. June 22, 1990 U Programming Language OBJNUM listelem(OBJLIST,NUM) - returns the object number of the N-th element in the list. Unlike C, U lists are indexed with the first element being element number 1. If the index number given is higher than the total number of object numbers in the list, or the first parameter is not a list, NULL is returned. OBJLIST listmerge(OBJLIST,OBJLIST) - returns a new object list, consisting of the union of the lists provided as parameters. Duplicates are suppressed. As a special case, either of the two lists provided as parameters can be NULL, which will effectively return the other list. OBJLIST listnew(object#1,object#2,object#N) - returns a new object list, consisting of the object numbers given. If no parameters are given, or all the provided parameters are invalid, an empty list is returned. NUM listsearch(OBJLIST,object#) - returns the index offset of the given object number in the list, or NULL if the object number is not in the list. NULL is returned if a parameter is missing, or is of the wrong type. Since list element offsets start at one, locating an object number in the first 'slot' of the list returns numeric '1'. OBJLIST listsetelem(OBJLIST,object#,NUM) - returns a list, with the object number at the numeric index set to the pro- vided object number. If there are not that many object numbers in the list, the original list is returned unchanged. NULL is returned in the case of a missing parame- ter, or incorrect parameter type. 9.3. Object Manipulation Functions NUM objectdestroy(object#) - destroys the numbered object, if the caller has permission, destroying any object elements bound to that object as well. NULL is returned in the event that permission is denied. In the event of a successful annihilation, numeric zero is returned. In some implementa- tions, this permission requires that the caller's effective user-id or real user-id be Creator. This is to avoid prob- lems with players destroying objects that people are hold- ing, etc, and is the default case. NULL objectelements(object #) - lists the elements defined in an object to the caller's terminal. The caller must have read permission on the base object, in order to invoke this function. OBJNUM objectnew() - creates a new object and returns its number. The new object is the property of the caller, and is created empty, with default permissions. OBJNUM objectowner(object#, or reference to object element) June 22, 1990 Copyright, Marcus J. Ranum, 1990 - returns the object number of the object owning the root object or object element provided. In the event of the object or element's non-existence, NULL is returned. 9.4. Permissions and Environment Manipulation Functions NUM chmod(object#,or reference to object element,STR) - sets the permissions of the root object or object element, if the caller is the owner of the root object or object element. The string can contain a coding of permissions values con- sisting of any of: 'O', signifying owner, 'I', signifying indirect, and 'W' signifying world, followed by a colon, and any of 'w' for write, or 'r' for read. Functions may have a setuid specifier applied to the world permissions set, by using 's'. For examples, see above. In the event of failure, NULL or returned; numeric zero is returned if the operation was a success. NUM chown(object # or reference to object element,OBJNUM) - sets the ownership of the specified root object or object element to the object number provided. The call will fail if the caller is not the owner of the object being given away. If a setuid bit is marked in the object's permissions it is cleared as part of this process. In the event of failure, NULL is returned; numeric zero is returned if the operation is successful. OBJNUM geteuid() - returns the object number of the current caller's effective user-id. OBJNUM getuid() - returns the object number of the current caller's real user-id. NUM setruid(OBJNUM) - sets the real user-id of the current calling function to the object number specified as a parame- ter. This should only be used with extreme caution in rare circumstances, as it affects the value returned by #actor (unlike setuid). Its effect ends when the current call is completed. If called directly from the monitor, the real user-id is not permanently changed. This function can only be called if the real or effective user-id of the caller is the Creator. Successful calls return numeric zero; failed calls return numeric one. NUM setuid(OBJNUM) - sets the effective user-id of the current calling function to the object number specified as a parameter. This call will only succeed if the real user-id of the caller is the number being set to, or the caller is Creator. In the event of failure, NULL is returned; numeric zero is returned if the operation succeeds. 9.5. Matching Functions OBJNUM match(STR,OBJNUM or OBJLIST,STR,[OBJNUM or June 22, 1990 U Programming Language OBJLIST,STR]..) - match scans the object list, accessing each object number in the list for an element name matching the provided string. An arbitrary number of object list/string pairs can be provided. Once each element is accessed, it is compared against the first string given. The object number of the best match found is returned. In the event of multiple matches, the returned object is selected based on the order in which the lists were passed to the function (priority given the the first list). If a complete match is made at any point, no further searching is done. This is an especially useful function for matching a user's input with objects in the surroundings: ... $nam = match("foo",#actor.carr,"name",#actor.loc,"name"); if($nam != NULL) { ... In the example above, a search is being made for things that match the word "foo". The first list searched is the #actor.carr list - each object in that list is checked for a string element named "name". After #actor.carr is searched, #actor.loc is searched, also for string elements named "name". In the example above, if #actor.carr had the object numbers #44 and #55 in the list, match would check to see if there was a string stored in element #44.name, then #55.name, and so on. Individual root object numbers can be passed to match, as well, so that: ... $nam = match("foo",#99,"name"); if($nam != NULL) { ... will return #99 if there is an element named "name" in #99 that is "foo", or begins with "foo". Individual root object numbers can be freely combined with lists in calls to match. An additional aspect of matching is the magic match character, which can be used in strings to effect an "alias" of sorts. The match character is inserted in a string with an escaped semicolon: '\;'. This causes match to re-start matching a string whenever it encounters the semicolon. This is useful in many ways. ... #99.name = "Rusty\;doofus"; $nam = match("doof",#99,"name"); if($nam != NULL) { ... In the above example, match will retrieve #99.name and June 22, 1990 Copyright, Marcus J. Ranum, 1990 compare "doof" with "Rusty", generating a failure. The match character is then detected, which causes match to re-match against "doofus", which succeeds and returns #99. 10. Programming Hints, Traps, and Pitfalls. The mistake most commonly made by the author is to store a variable within an object, which has the same name as a function within that object. The net effect of this is to destroy the function when it is called. This is surpris- ingly easy to do: /* a function to turn #44 on, whatever #44 is */ func #44.on { ... /* if the thing is turned off, turn it on.. */ if(#44.on == NULL) { #44.on = 1; } ... } The function runs perfectly, the first time. Subsequent calls try to call #44.on (which is now the numeric value one) and return NULL (no such function). Realizing that one has done this in a program is infuriating in the extreme. 11. Annotated Programming Examples 11.1. A Room With Lights The following is an example of a room with a light switch that works. Assuming here that room #1 has already been created, and so on, the programmer binds a function to it as a light switch: func #1.lights { if(#self.on == NULL) { $tmp = #self.on = 1; if($tmp == NULL) { echo("the switch is broken0); return; } @.emote("turns on the lights"); #self.desc = "You see a dusty room, [...]"; return; } #self.desc = "It is pitch dark in here"; #self.on = NULL; @.emote("turns off the lights"); } chmod(.lights,"W:rs"); June 22, 1990 U Programming Language The function checks to see if object element #self.on is set - using that as a boolean value for the state of the lights. Note that here, it is set and unset to NULL rather than a value. When no value is desired, using NULL and non-NULL is faster and saves database space besides. If the rooms lights are off (#self.on == NULL), an attempt is made to set the lights on. The value of the assign is checked to make sure that the operation was successful ($tmp == NULL implies a broken switch). If that operation was successful, a call is made to one of the universe's functions 'emote' (@.emote) which presumably sends a string to anyone else who happens to be in the room. The room then alters its description to adapt for the lights being on, and returns. If the lights were already on, the first case fails, and the lights are turned off by resetting the description of the room and set- ting the value of #self.on to NULL. 11.2. More to come. More good examples to be added, when people create small nifty objects. June 22, 1990