[NOTE: Almost ALL of the information in this file is out of date and is no longer correct. All the suggested modifications have already been made, or their features are implemented in different ways. This document is provided only as an interesting historical note on the developement of TinyMUCK.] TinyMUCK 2.0 Technical Notes Lachesis, June 1990 E-Mail: lachesis@belch.berkeley.edu INTRODUCTION The goal of this work was to give TinyMUCK a programming language. I had been playing around with MUCKs and MUDs for all of two weeks, when, upon creating a movie theatre, I found the supporting world in adaequte. This note is intended for those who wish to add to the TinyMUCK code some more. (Especially the compiler and interpreter.) It discusses the design of the programming language, its implementation, and also the possible extensions that would be welcome. DESIGN During the design stage, primary consideration was given to ease of implementation, reliability, extensibility, and speed. Secondary considerations included ease of use and consistency with the rest of the TinyMUCK paradigm. In particular, I did not want the programming language to seem like a kludge. (By definition it *is* a kludge, but I did want it to possess some elegance.) First of all, I needed ways to add to an object's data fields without having to change source code. To facilitate this, I came up with the concept of property lists --- basically character string pairs which were settable by the user. The data structure also included a hidden integer field which could only be accessible through programs. This allows the programmer to make certain things inaccessible to the user (except for examining) Then, I had to decide on a programming language. I observe that most MUCKs use a sort of C-like language, which I don't really like. (though I'm a fairly experienced C programmer) Part of it is that C isn't really all that great for prototyping, and its syntax isn't exactly the easiest thing in the world to parse. I hovered between LISP and forth for a while, before coming down on the side of forth because it's easier to write a byte-compiler for forth than it is to write one for LISP. Note that I had no experience with forth whatsoever, aside from a bit of experience in thinking about stack machines, but I picked up the language easily enough. The syntax took all of 5 minutes to learn and an hour to sink in. I decided that I wanted an editor, (partly because lots of people on my MUCK don't even have tinytalk) and went about implementing a line-based editor. The problem comes in, of course in reading input --- how am I supposed to know that something is meant for the editor (or a forth program) and something is meant for the command parser? My solution was to add a flag called INTERACTIVE. When this flag is set on a player, I will pass the command to the editor or the forth program depending on the state of the player. I then added a few more fields to the player structure, and added a new program type called TYPE_PROGRAM. Each player also had a frame in which to contain the forth program's execution image. It turned out that for each player to have a frame permanently allocated to him would take up too much space, so I made frame allocation dynamic after loading up "Atlantis," my TestMUCK, and finding that it took up 5 Megabytes of RAM. IMPLEMENTATION I implmented the editor and the compiler independently --- as far as they are concerned, the only thing they have in common is the data strucuture containing a program's text. Together, the implementation took about 2 man days, or 1 weekend. Testing and debugging then took another day. The editor is a simple line editor, with each line held in a doubly linked list. Commands are available to list the lines, delete lines, insert lines, compile the program, and disassemble. The compiler was also simple --- it had no look ahead, (other than recursive calls to itself) and was single pass --- all references were resolved by a back-tracking mechanism, which basically left blanks in the byte-code and filled them in as the answers became clear. It was at this stage that the ELSE clause of the IF was abandoned --- it is still possible to implement it --- I simply did not feel the need, nor did I want to take the time. One of the more interesting aspects of the language, though, is that recursion is supported. As far as I know, most other MUD languages do not implement recursion. Given my stack based structure, however, I knew that it would be impossible to hit an infinite loop without overflowing the stack in a very short time, and therefore allowed that. In the future, tail-recursion elimination could be implemented, though doing that means that a program would have to keep track of how many cycles it has taken up and abort or suspend its operation after a certain number of executed instructions. The READ capability of MUF is, I believe, quite unique. It means that things such as the purity test can be implemented with little difficulty. (And even less inflexibly than you might think, especially if the property list of a room was used to store the questions instead of hard-coding them into the program.) PUTTING IT IN This part was harder than I thought, for there were many places where things had to happen. For instance, because the programming language could do so many things, (including give players pennies or take away pennies from players) I added a MUCKER bit to the flags so that only MUCKERS or WIZARDS could program. I also had to add to the database routines the capability of reading in programs. Here too, TinyMUCK is unique in that it stores not the byte code, but the source code. Each MUF program is stored in a separate file, and while it takes extra time to load the file, I considered the time spent quite worth while --- in the event that a large forth package needs to be written or added to a program, a player could mail the program to the wizard who could just stick it in. Furthermore, the primary Wizard can read the source code on-site and modify it if she finds that it violates her guidelines or whatever it is that she wishes to enforce. Upon start up, each program encountered is loaded into memory and an attempt to compile it is made. Compile errors are sent to the standard error at this point, since no players are on line. Everything then proceeds as normal. Programs are created using the @prog directive, which puts you automatically into the line-editor. Within this line editor, you may compile, edit and disassemble code. (Disassembly was really a debugging patch put in so I could see the compiler output without executing it.) Actions or Exits can then be linked (but NOT meta-linked) to programs by the usual means --- you must own a program or it must be LINK_OK before you can link to it. A successful use of the action/exit then launches and executes the program. Note that while you can see your own programs, you cannot see another's programs, making programs effectively dark. Programs can also be part of locks, in which case the program is launched when a test is made. Note that should a program attempt to READ or causes an error during execution, the test is automatically false. (Or rather, the program part of the test is automatically false.) FUTURE ADDITIONS I'm taking a break from hacking MUCK code after this, so this is a list of what enterprising hackers might want to do with my code. Please try to contact me before doing things so that I can help coordinate all new MUCK features and modifications. 1. Extend the compiler. A hash table should be used for the primitives. As the primitive list gets longer and longer, the current method will no longer be sufficient to guarantee speed. 2. Add primitives to the interpreter. This is the easiest to do transparently, as the only changes (if they are non-drastic) would occur in get_primitive() in compile.c (which has the list of primitives), and dispatch() in interp.c, which defines each primitive. A method for using exits would be nice, and would allow things happening in UberMUD (like a remote-controlled tank), which aren't allowed right now. Part of the problem has to do with how to trigger exits --- they shouldn't be links to programs, for instance, as it might result in a recursive infinite loop or worse... Before adding primitives, please contact me --- I will issue you a range of numbers to use (if you know how many you intend to implement, please state so --- but give yourself some room.) so that different hackers don't wind up on each other's turfs. Yes, I do know that with #defines it is easier to rearrange things, but I'd prefer to have it easy. It would be nice to have a CALL that does not inherit variables. (But it MUST NOT have it's own stack, or launch another frame.) Local primitives should always be 32000 or above. (But below 32767, the maxint for shorts) 3. Extend the programming language's capabilities. Adding primitives is one way, but if you look at the code, you will notice a TYPE_DAEMON, which is a type of program that runs on time rather than on user command. If you can think of a good way to implement it, do so and let me know when you're done. It should not be too hard, but does need some careful thinking. 4. Write a meta-compiler to MUF Write a front end to Forth to translate from, say, C to MUF. I do not ever intend to implement C in the server, as this takes up too much time, but this translation to forth should be relatively straight-forward, and takes lots of load off the server, which is nice. 5. Add a GLOBALIZE ACTION command This will allow wizards to add actions that are accessible to everyone implicitly. This will allow new commands to be added to TinyMUCK by writing MUF code and then globalizing an action linked to that code -- extending the compiler/interpreter further would make it at least competitive with any UberMUD. (By allowing programmed actions to shadow built-in actions.) ACKNOWLEDGEMENTS Thanks and Kudos to Stephen White, James Aspnes, and other TinyMUD/TinyMUCK contributors for making this possible. Thanks to all the programmable MUDS out there for giving me the incentive to come up with something like this to keep MUDs/MUCKs alive if nothing else. And finally, thanks to everyone on Atlantis, who kept my morale and interest high even on the worst of days. (even during the crashes --- you've been an exceedingly patient audience, and I hope that all the feedback I receive from now on would be as constructive as that.)