Capturing Calls to Undefined Properties

The TADS 3 language doesn't require you to specify the types of variables, functions, and properties when you declare them – in fact, the language doesn't have any way of making these declarations even if you wanted to.  This means that the language is not "statically typed": you can't tell the type of a variable with certainty just by looking at the variable's definition.

 

(Note that this isn't to say the language is "weakly typed"; a weakly typed language is one that allows different types to be used more or less interchangeably, so that the interpretation of a value depends on the context in which it's used.  In a weakly typed language, it's always up to the programmer to specify the correct interpretation for a value each time it's used; for example, you might have to call one function to display a value as a string, and a different function to display the same value as an integer.  TADS 3 is a strongly typed language, but it is not statically typed; it is instead run-time typed, which means that each value has a fixed type that cannot change, but a particular variable can hold values of any type.  In a statically typed language, the variables keep track of the types; in a run-time typed language, the values themselves carry the type information.)

 

Because the compiler doesn't know in advance what kind of object a variable might contain, the compiler can't determine whether or not a particular property will be defined for the object.  For example, consider this code:

 

  local x;
  x = getSomeObject();
  x.name;


Because the compiler can't tell what kind of object x will contain when this code is executed, the compiler can't know whether or not that object will define the property "name."

 

When you call a property (or, equivalently, a method) on an object, and the object doesn't define that property and doesn't inherit it from any superclass, the VM will do one of two things:

 

Throwing an Exception for Undefined Properties

Some library authors might decide that calls to undefined properties are inherently incorrect, and so choose to treat such calls as errors.  The propNotDefined mechanism can be used to accomplish this, as shown in the code below.

 

  // this export is needed only if the library doesn't
  // otherwise define it
  property propNotDefined;
  export propNotDefined;
 
  // an exception for invoking an undefined property -
  // note that reflection could be used to provide a better message
  class PropNotDefinedException: Exception
    construct(prop, argList) { prop_ = prop; argList_ = argList; }
    displayException() { "call to undefined property"; }
    prop_ = nil
    argList_ = nil
  ;
 
  // throw an exception for any undefined property invocations
  modify Object
    propNotDefined(prop, [args])
    {
      throw new PropNotDefinedException(prop, args);
    }
  ;

Proxy Objects

In some cases, it is useful to define one object as a "proxy" for another, so that the proxy object redirects most method calls to its underlying object.  This allows the proxy to provide its own definitions for a few particular properties, while letting the original object do everything else.  The propNotDefined mechanism makes this easy to implement.

 

  // redirect everything but 'name' to the original
  class Proxy: object
    construct(original) { orig_ = original; }
 
    // change the name
    name = "proxy for <<orig_.name>>"
 
    // redirect everything we don't define ourselves
    propNotDefined(prop, [args])
    {
      // call the undefined property on the original object
      orig_.(prop)(args...);
    }
 
    // my underlying object
    orig_ = nil
  ;