?

Log in

TADS 3 System Development

Recent Entries

TADS 3 System Development

View

Navigation

January 25th, 2016

QTads 2.1.7 is now out and can be downloaded at:

http://qtads.sourceforge.net/downloads.shtml

Linux, Mac and Windows versions are provided, as well as the full sources.

Note that starting with this version, Mac OS X 10.6 is no longer supported. You'll need 10.7 or higher.

Changelog since 2.1.6:

  • The TADS virtual machines have been updated to 2.5.17/3.1.3.

  • Added support for Mac OS X high resolution (retina) displays.

  • The interpreter will now try to detect whether a game is stuck in an infinite loop when quitting. This should avoid the situation where the QTads window is closed, but a process is still running in the background, consuming CPU cycles.

  • Selecting text with the mouse is now supported, as is drag&drop of text to and from the game window. Furthermore, you can paste the current word under the mouse cursor instead of selecting it by holding down the "Ctrl" key ("Command" key on the Mac) while double clicking. This behavior can be disabled in the configuration dialog in the "Misc" section.

  • On Mac OS X, the main window should now properly get focus again when closing a dialog.

  • Text-grid banner windows should now be able to correctly show their background color instead of always using black.

  • Printing a table without any cells in it will no longer result in the screen getting "stuck" and failing to update.

  • Fixed a problem with the online update check, which would result in a connection error, or sometimes an application crash. Thanks to Denis Pokataev for finding and fixing the crash case.

  • Added Ctrl+F command history search. It finds previous commands that begin with the same text as the text that is currently to the left of the input caret. If multiple matches are found, repeatedly pressing Ctrl+F will cycle through all matches. Thanks to tetzank for the patch.

September 24th, 2012

adv3Lite Blog

Share

Rather than burden this blog with any further postings about adv3Lite, I've started a new one at http://ericeve.livejournal.com where I'll continue to post about developments and where people can leave comments and feedback.


If you visit the blog you'll see I've just posted there providing a link to the half-completed adv3Lite documentation, in case anyone's interested in seeing how the project is shaping up.



—Eric Eve

August 28th, 2012

One of the bug fixes in 3.1.2 created a new bug, which caused Workbench to crash when running a Web UI game more than once. I've posted an updated version (build Win119) of the Windows Author's Kit that fixes the bug. The bug only happened with Web UI games under Workbench. The new version doesn't have any other changes, and doesn't affect the interpreter or compiler, or any platforms other than Windows.

August 21st, 2012

TADS 3.1.2

Share
I've posted a minor TADS update, which you can download from tads.org. This one just has a few bug fixes and some minor feature additions.

August 9th, 2012

I just wanted to reiterate a point that might have lost in the details in my earlier Mercury posts: the reason I published it is to open it up for collaboration and spin-off projects. I might not have time to do any more work on it myself for a while - indeed, it's been stalled in its current state for a couple of years now - so the main reason for publishing it is to allow it to move forward if anyone else is interested in making that happen. Please let me know if you're interested and you have the bandwidth to take on a chunk of it; depending on the level of interest and the tastes of those involved, we can figure out what makes sense in terms of source control and other tools. (If you want to use it as the starting point for a separate project on your own, no need to coordinate with me or anyone else, but I'd still appreciate hearing about it.)

August 4th, 2012

As promised, more details on the Mercury library I posted yesterday.

First, I should outline my goals with the Mercury project. It was never simply a port of the TADS 2 library to TADS 3 - that would actually make a great separate project, and I think the Mercury parser would serve as a good starting point. If someone wants to pursue that, I'd be all for it. But what I had in mind when I started working on Mercury was a hybrid agenda: I wanted to combine the simplicity of the TADS 2 world model with some of the more important features of the Adv3 parser and command system. I also wanted to fix some of the key deficiencies of the TADS 2 world model; many of those were problems with design rather than oversimplifications, so I felt that I could make improvements without necessarily adding any complexity.

Despite the hybrid goals, Mercury isn't not a hybrid code base. It doesn't inherit any actual code from either the TADS 2 library or Adv3. It doesn't even directly inherit most of the design features of either; as I got going on the project, I tried to re-examine the design of each component, rather than just taking a TADS 2 or Adv3 motif and transplanting it into the new system. So a lot of what's in Mercury is new at the design level - which makes the new library untested not just at the lines-of-code level.

Next, an overview on the major components.

Parser: this looks superficially a lot like the Adv3 parser. It's based on a network of grammar rules using TADS 3 'grammar' objects, because I think the 'grammar' scheme is the right tool for this job. The set of grammar rules in Adv3 is large and complex, but I don't think there's any way around that; English grammar is large and complex. Mercury defines basically the same grammar as Adv3. However, Mercury has what I think is a much smarter division of labor. If you look through the Adv3 English grammar, it has a ton of code defined in the 'grammar' objects. In contrast, the Mercury English grammar has almost none - it's almost purely declarative, just a list of token processing rules. The difference is in how the two parsers represent the results of parsing. Adv3 used the "sentence diagram" that comes out of the grammar tree as the result. Mercury builds what compiler writers call an abstract syntax tree, or AST. An AST operates at a higher level; it represents the semantic elements of the input rather than the tree of words and phrases. This approach greatly simplifies the code that analyzes the parse results. I've found the Mercury parser much easier to work on than the Adv3 parser, and I think game authors will find it much easier to customize.

I've tried to keep the parser's dependencies on the world model to a minimum. The parser obviously needs to interrogate the world model to some extent in order to resolve noun phrases to objects, so some interaction is a given, but I've tried to minimize the interface so that the parser can be used in other offshoot projects.

One slight expansion on the Adv3 parsing model is explicit support for three-object verbs, with a direct, indirect, and "auxiliary" object: PUT THE COIN IN THE SLOT WITH THE TONGS. This is one of those perennial wish-list items that was always a huge pain with Adv3 (and practically impossible with TADS 2), but the new way of representing parsed output actually made it pretty easy to add these, so I did for the sake of completeness. It would probably be equally easy to have four- or ten-object verbs as well, but the playability implications for even three-object verbs are bad enough that I think we can safely rule out the N-object case for all N > 3.

Spelling corrector: Mercury has spelling correction baked into the parser. This of course uses the edit distance calculator built into the Dictionary class - which makes the basic task of producing a candidate correction pool very fast, allowing the correction code in the library to do a lot of higher level analysis to pick good matches. If you fire up the sample game, try playing around with typos - I think the corrector does a really good job, but it'll be interesting to see how it does in actual play.

Parser questions: When the parser asks a question like "Which book do you mean?" or "What do you want to unlock it with?", it uses this neat little feature, which is similar to Special Topics in Adv3 but better generalized.

Distinguishers: Similar to the Adv3 concept, but the implementation is simpler.

Noun phrase parsing: Noun phrase parsing is both simpler and more sophisticated than in Adv3. It's simpler in that the grammar for a noun phrase is just a list of the words attached to an object, which is similar to Inform 6's approach. This makes it a lot easier to define unusual phrasings like "key to the house" (where we have a preposition in the phrase), "science and progress magazine" (a conjunction in the phrase) or "button 6" (inverting the usual English adj-noun order). Making the noun phrase grammar totally generic with respect to word order means that there's no need to define new grammar rules for unusual phrases like those. But, hey, this is TADS, and we like to separate our parts of speech. So when it comes to resolving noun phrases, the parser still analyzes them according to the parts of speech, so it can still distinguish "pizza" from "pizza box" on the strength of the adjective/noun usage.

(I think this is a successful example of what I've been trying to accomplish with the project: it simplifies the game programming model without reducing the functionality.)

Object states: The parser has a formal mechanism for handling object states - lit/unlit, open/closed, red/green/blue. States are automatically used to resolve noun phrases, so "take the lit match" works without any special coding in the match. There's a State class that defines a state and its associated vocabulary; in order to have a state, an object simply defines the property for the state, such as isLit or isOpen, so in terms of the game programming there's practically no work.

Commands and Doers: These objects are the heart of the execution model. A Command is essentially the AST (see above) for a parsed verb, with slots for the Action (verb) and objects. It's comparable to an Action in Adv3, but it's a bit cleaner in that it's a synthesized object rather than a parse tree. Doer is a new concept. This is where the library and game will define the handling for a particular command. A Doer is a global object that has a template for a command to process; the template is given as a string, which can name verbs, object classes, and individual objects. If you look at the sample/test program, you'll see several examples at the top. Here's one:

Doer cmd = 'put Thing in Thing';

That says we should match the 'put in' command when a Thing is the direct object and another Thing is the indirect object. You can define a more specific one, like

Doer cmd = 'put Thing in Container';

To execute a command, the parser scans the population of Doers for the best match, and then calls its exec() method. There's of course a hierarchy of priorities, one of which is object class specificity - so the Container match will override the Thing match. Another is an explicit "priority" property for times when you want to coerce a particular processing order. There are also ways to attach conditions to a Doer to limit it to specific times or places. This is somewhat similar to the Inform 7 rulebook scheme, but it's simpler and more transparent. This is obviously a new, untested approach, so it remains to be seen if it's effective and scalable. I like it so far, though. One aspect that makes me think it's promising is that it can straightforwardly degenerate to a traditional TADS 2/3 model where everything gets dispatched to the objects: you'd just write the exec() method to delegate to the appropriate objects in the Command object. And then you'd still be able to use Doers to write a few custom overrides at the verb level, before the objects got involved, which is something that got tacked on in the Adv3 model because it's an occasional necessity. But I think it'll turn out that this is a better way to write command handlers in general - especially for two-object verbs, since it creates a formal abstraction point that's separate from either object but aware of both, eliminating the usual confusion in the object-oriented approach over which object's handler runs first and all that.

The rest of the execution model has yet to be fleshed out, along with all of the default handling for all of the basic verbs. For 90% of the pre-defined verbs, a generic 'verb Thing' Doer with an exec() method that displays suitable apologies should be all that's required.


Queries: This is another big new concept (along with Doers). This is another bit of I7 rulebook influence. The idea is that when we have a question about the state of the world model, such as "can actor A see thing B?" or "what's in scope to actor A?", we don't ask the objects involved directly, but instead ask the "world model" as a separate entity. As with Doers, this solves certain problems that the OOP approach has when multiple objects are involved in the questions: with OOP, we always had to worry about whether we ask A if it can see B, or ask B if it can be seen by A, and whether the answers would be consistent. And as with Doers, the scheme allows for a hierarchy of query handlers, so that a particular query's answer can be overridden based on location or current conditions.

The query mechanism looks promising to me in its current state, but I think it might need a few more passes to get right. When you look at it, try to think in terms of the abstraction rather than the syntax, because I don't think this part is quite ready for prime time.

Containment model: I couldn't resist trying to enhance the containment model a bit - another perennial wish-list item. The TADS 2 and Adv3 way of handling different containment types (on, in, under, etc) is to make the type of containment an attribute of the container. That seems simple, but it's not a very match to reality, and it makes things terribly complicated when you want an object with an inside and a top. So this time around I've moved the containment type to the child - so each object has a 'location' property and a 'locType' property that says what type of containment relationship it has with its location. This makes all of the containment code in Thing somewhat more elaborate, but my hope is that it won't matter much to game code, where the main change you'll see is that you can write things like moveOnto() or moveUnder() in addition to moveInto().

Sense model and scoping: This part is pretty much pure TADS 2. The Adv3 sense model with its partial transparencies and so forth is a huge source of Adv3's complexity, and also takes a heck of a lot of computing horsepower. I figured if the library is supposed to be lighter weight, this is the number one thing to cut in terms of the cost/benefit tradeoff. However, I don't think there will be a ton of lost flexibility, thanks to the Query mechanism above - I think that when it came to actual practice, most of the uses for the more advanced Adv3 features were essentially special cases anyway, and the Query scheme should be able to handle those more easily.

Prerequisites: Essentially the same as Adv3 preconditions, but a little bit more generalized, to allow for enforcing condition checks even when they can't be automatically brought into being with an implied command. I think preconditions are among the more useful upgrades in Adv3 vs TADS 2, so I think it's important to keep these in the model.

Output processing: The output processor is much simpler than in Adv3. This might need to get beefed up as the library evolves, but I'd prefer to see this part stay simple; the complex layering of output filtering and capturing in Adv3 is much too fragile, so I'd rather look for other ways to handle as much of that as possible - other ways that don't involve output filtering, but instead making decisions about output before generating it. The output handler does have a good infrastructure for parsing HTML, though, so it'll be technically straightforward to add HTML-level processing as needed.

There is, of course, a good message processor that handles object phrase substitutions, narrative person options ("I don't see that here" vs "You don't see that here"), and verb tenses. I got kind of carried away with the verb tense handling and implemented six English tenses (present, past, perfect, past perfect, future, and future perfect); this was more because the design was flexible enough to accommodate it than for any practical reason, since I very much doubt anyone will want to write a game that says things like "You will not have seen any blue books there." I really should add the subjunctive mood for real completeness.

The message processor is similar to the one in Adv3, although it's actually a bit nicer, I think. Apart from triple the verb tenses, it has a friendlier approach to handling verb conjugation, which is that it uses a dictionary for irregular verbs rather than making you write conjugations in-line in every message. The syntax for writing a message is thus a lot simpler and more readable. The substitution syntax has some other nice features, such as positional parameters that let you use the message processor even when you don't have a Command in progress (with Adv3, the message processor depends on an Action being in progress as the source of the object parameters), spelled-out numbers, and in-line list making. It also lets you define the "terse" version of message in-line with the full message, as in 'Taken.|{I} {pick} up {the dobj}.'.

Translations: Mercury uses the Adv3 approach to translations, of placing the language-specific code in a separate set of English files. Hopefully the size of the translation task will be scaled down from Adv3 in proportion with the rest of the library.

One change from the Adv3 approach is in how message text is coded. In Adv3, message text is all coded as strings in the English message objects. In Mercury, English message text is entered in-line where the message is actually used, using the DMsg macro. The macro takes the actual text of the message along with a unique message ID tag (which is just a string) and any positional substitution arguments for the message. Don't worry - this doesn't mean messages are hard-coded as English. When you compile with LANGUAGE set to English, the macro uses the English text. When you compile with any other LANGUAGE value, the macro generates a lookup using the ID tag. Each language module provides a table of ID tags to message string mappings. The nice thing about this approach is that it makes the code a little easier to read by letting you see the actual text of each message where it's actually generated, rather than having to cross-reference against a separate message list (msg_neu.t in Adv3). I think it'll also make the job of translating slightly easier: the DMsg macro makes an easy grep target, so translators can still easily find all of the messages, but now they can see each English message in its original context without having to cross-reference backwards from the message file to the point(s) of use.

Utilities: util.t has a bunch of useful utility functions and patterns. Among the more interesting: (1) a function prototype matching system that makes it fairly easy to write functions and methods that take different types of parameter lists, and figures out which actual list was passed in. (2) a static object construction system that unifies constructor calling for static and dynamic objects, so that you don't have to create nearly so many explicit PreinitObjects. (3) singleton iterators, which let you invoke a for..in loop on any object, whether it's a collection or a singleton; if it's a singleton, the loop just runs for the single object. (4) a general-purpose list differencing engine (the spelling corrector uses this to display the list of corrected words, but it can be used to get a diff report for any pair of lists).

...

That's most of what's implemented so far. There are a few things missing before this can be used to write anything real. The biggest piece is that the command execution model needs to be completed, and default handlers written for the standard verbs (take, drop, put in, travel, etc). The other big piece is that the code to generate room descriptions and item listings is only sketched out.

There's also a much less than half-baked idea, maybe 1% baked, that I started thinking about and haven't gotten back to yet, in the form of the Effect object. I'm not sure if this is going to be worth pursuing. The idea is that an Effect object represents an executed command; it's the counterpart of the Command object after the command has been carried out. A Command represents an intention to perform some action, and an Effect represents the result of performing the action. So rather than generating text output directly, a command handler would create an Effect object. There'd be a separate Effect subclass for each unique thing that can happen in the game; so, for example, there'd be an Effect subclass that represents an actor taking an object (the actor and object would be properties of the Effect, so there'd be just one Effect subclass for "take", not a separate subclass for each object that can be taken). The Effect object encapsulates the code for executing the operation and a message describing the outcome.

The point of Effect objects is to do something like the Adv3 transcript, but representing things at a higher level to make post-processing easier. Adv3 manages some neat tricks with the transcript mechanism, but the code to pull off those tricks can be awfully hard to write and easy to break. But I'm not sure if Mercury should have any sort of transcript intermediary at all; there's something to be said for the TADS 2 way of doing things where command handlers just do the work and display the results, and those results hit the display immediately without going through miles of convoluted internal plumbing.

August 3rd, 2012

Is there anyone with a work-in-progress, or a work-about-to-be-in-progress, who'd like to test out locational naming in practical use? Given the pitfalls Nikos and others raised, I think it'd be a good idea to give it a go in real-world use before adding it to the language. If anyone wants to give it a try, drop me an email and we can talk about it.
A couple of people have asked recently about the "Mercury" library I
mentioned I was working on quite a while back. If you missed those
posts, Mercury was conceived as a replacement for the Adv3 library
that's closer to the TADS 2 level of complexity. It's not a port of
the TADS 2 library by any means, but it's meant to be comparable to
TADS 2 in terms of learning curve and programming workload. This
would let people who are happier with the TADS 2 way of doing things
migrate to the TADS 3 language and VM, which have a lot of benefits
separate from the the Adv3 library.

The work I did on Mercury back at the time of those posts motivated a
lot of the enhancements I've been adding to the language in the
meantime. Unfortunately, the language work has taken all my time, so
Mercury has been pretty much in cold storage. I've finally wrapped up
most of the language work I had in mind (although the list keeps
growing the more I work on it), so I'm at a point where I could turn
to Mercury again.

Rather than resuming the Mercury work in my secret underground
laboratory, though, I'm going to try something different and publish
what I have so far, incomplete and half-baked as it is. I have two
things in mind publishing it. The first is collaboration: a couple of
people have expressed interest in actively joining in on the project.
There are enough independent pieces in this project that I think a
collaborative approach could work well to elaborate it from its
current state into a full library.

The second is to provide a starting point for independent library
projects. Adv3 is a big scary monolith. Mercury has at its core a
fairly modular parser. If, for example, someone wanted to more or
less literally port the TADS 2 adv.t library to TADS 3, I think the
Mercury parser would make a great starting point by standing in for
the built-in TADS 2 parser. I think you could combine the Mercury
parser with the TADS 2 world model and command execution model and
have something going in fairly short order.

I want to emphasize that the library in its current state is
completely unusable for writing games. It's not done or even nearly
done. Some parts (notably the parser) are mostly there, but many
components that are absolutely necessary for writing IF are either
missing or are just placeholders. I've built it and done a little
playing around to make sure it's mostly working with the latest 3.1.1
build, but be aware that this isn't production quality code, or even
beta, or really even alpha - I don't think they have a Greek letter
for this level of roughness.

Here are the downloads:

Mercury library

sample/test program

I'll post shortly with some more details on (a) what's in the
current library, and (b) my ideas for completing it.

July 27th, 2012

Locational naming

Share
I'm finally getting around to a language feature I've been meaning to add for a long time now. The design is pretty straightforward, but I'd like to get some opinions before settling on the exact syntax.

I'll start by describing the feature at a high level, then I'll get into the syntax details.

First the motivation. Object naming in larger TADS games gets a little tedious, mostly because all object names are global. For a common object like a table, it'd be nice to be able to name it something simple like 'table', but we usually can't because we might have a few other tables scattered around the game. The usual way I deal with this is to choose fairly long names for items, doing something like combining the room name and the item name: kitchenTable, say, or iceCavePedestal. That solves the problem of keeping names unique, but at the cost of making the names hard to read and tedious to type.

(Note that anonymous objects were motivated by this same problem, and went a long way toward solving it. Anonymous objects neatly deal with the very common case of self-contained objects that no one else needs to refer to directly, such as decorations and components. But anonymity doesn't work when we need to refer to an object from code belonging to another object. That happens often enough that the object naming nuisance is still with us.)

Given this pattern of ad hoc object naming by location, how could make it more automatic, and also improve readability and reduce our keyboard workload?

The compiler already has some awareness of the game world's containment structure, thanks to the '+' syntax for object definitions. That syntax lets us mirror the containment structure of the game world in the lexical structure of the source code, in a fairly natural way. The idea behind the new feature is to take this compile-time containment structure and use it to partition the object namespace. Rather than having to *explicitly* use a name like kitchenTable, it would be nicer to be able to call it simply 'table', and let the compiler remember that it's defined within the kitchen. If we also have a table defined in the parlor, it would be nice to be able to call it simply 'table' as well, and let the compiler sort out which is which based on context, and when there's not enough context, based on some new syntax that lets us tell the compiler which one we're talking about.

The details of the new feature:

1. Object names can be repeated, as long a name is only used once at a given containment level.

kitchen: Room;
+ table: Surface;
++ box: Container;

parlor: Room;
+ table: Surface;
++ box: Container;

2. Within an object definition, the naming context is established by the object's location. Within a parlor method, a reference to 'table' is the parlor table; within a kitchen method, a reference to 'table' is the kitchen table. This extends inwards and outwards, so that 'table' means the parlor table from within the parlor, the parlor table, and the parlor table box.

3. When the location context doesn't resolve an ambiguous name, we can explicitly name the object path. For example, in the code for livingRoom, outside of the kitchen and parlor namespaces, if we want to refer to one of those tables, we have to call it 'kitchen.table' or 'parlor.table'.

4. Similarly, if we want to refer to something outside of the current context namespace, we can use an explicit path. E.g., within a kitchen method, we can refer to parlor.table.

5. Paths only have to qualify things as far as they're unique, so we can refer to parlor.box, for example - we don't have to write the full path to parlor.table.box.

6. Globally unique object names never have to be qualified when referenced. If there's only one 'box' object defined in the game, there's never a need to use a location path to refer to it, no matter what the context - 'box' can only mean that one object. This is crucial because it means that existing code is seamlessly compatible with the new naming system. All existing code necessarily uses a unique name for each object, since reuse was always an error before, so all object references in existing code are already unique without any location qualifiers.

Now, on to my syntax questions.

When I started thinking about this feature, it seemed natural to use "." as the location path operator. This overloads the "." symbol, which of course also is used for property evaluation, but there's no ambiguity because of the rule that a given symbol can be an object name or a property name, but not both. If an object name is on the right side of a dot, there's only one possible meaning. There's also an excellent precedent for this within the C++ language family, in that Java uses "." as the package namespace scoping operator, which I think is a fairly close parallel.

If we relied on C++ alone as our syntax model, we'd probably choose "::" as the operator, since C++ uses that as its namespace scoping operator. The advantage of using "::" instead of "." for TADS location naming is that there's no ambiguity in the syntax - anyone looking at a piece of code would be able to tell that they're looking at an object location path, without having to know anything about the object names involved. With ".", there's no semantic ambiguity, but there's syntactic ambiguity - you have to know how the names are defined to know whether a given "." means property evaluation or location scoping. So a casual reader looking at a piece of code without having studied the overall context might misinterpret it.

Of the two, "." or "::", I like "." better aesthetically. I do see some value in the clarity of using separate operators for scoping vs property evaluation, but "." just looks a little cleaner to me somehow.

The wrench in the works, though, is that we need a global scoping operator, and I don't think "." will work for that. The global scoping operator is needed for situations like this:

kitchen: Room;
+ table: Surface;
++ box: Container;

box: Container;

We have a box within the kitchen, and a box out on its own at the top of the location tree. If we refer to just 'box' anywhere within the kitchen object tree, the context rule will always give us kitchen.table.box. If for some reason we really want to refer to that top-level box instead within the kitchen context, we need a global scoping operator - we need a way to say "the outermost 'box'". In C++, we'd write ::box. I don't think there's an equivalent with Java packages - or, rather, I think the equivalent in Java is that you simply have to move that outermost 'box' into a namespace if you want to be able to refer to it from within another namespace. I'm pretty sure I want to have an explicit outer scoping operator, though, mostly because it makes things a little easier to isolate when writing extensions and libraries.

Here are the options I see:

1. ::box, kitchen.box - use :: as the global scope operator only, and use . as the scope path operator. The upside is that most paths will contain only .'s because most paths won't need to shoot out to global scope, and when they do, it'll mostly be single element paths like ::box. The downside is the inconsistent representation of what are essentially two facets the same operator; multi-element global paths like ::kitchen.box make this especially apparent.

2. ::box, kitchen::box - use :: as the scoping operator in all cases. It's consistent, but I find paths like kitchen::box somewhat less aesthetically pleasing than kitchen.box.

3. kitchen.box - use . as the infix operator, and don't allow reusing an object name at global scope. There's no need for a global scope operator in this case because all ambiguous names are inside unique root objects.

The more I think about it, the more this strikes me as a reasonable and maybe even desirable restriction. Allowing reuse at the global level seems like a recipe for confusion when mixing code from multiple sources, like libraries and extensions.

4. outer.box, kitchen.box - use a keyword to represent the global scope, such as outer, global, unnamed, or anonymous. In principle I kind of like this approach, but in practice I haven't been able to come up with a keyword that I like for it.

Any thoughts are welcome.

July 16th, 2012

Nikos has released FrobTADS 1.2, which incorporates the latest TADS 3.1.1 release.  This means the new release is now available for Linux/Unix and OS X.
Powered by LiveJournal.com