Compiling and Linking

TADS 3 provides "separate compilation," which means that you can arrange your program's source code into several modules, each of which is compiled separately from the others, then link together the compiled modules into a finished program.

 

Previous versions of TADS provided a related feature, called "precompiled headers," that allowed you to compile part of your program's source code ahead of time.  This wasn't nearly as powerful as TADS 3's true separate compilation, however: precompiled headers only let you pre-compile a single section of the code, which meant that any change to the single pre-compiled section required full recompilation.  TADS 3's true separate compilation lets you structure your code into as many separate files as you want, each of which can be independently compiled; you only need to recompile the sections you actually change.

 

The TADS 3 compiler includes a "make" facility, which automatically recompiles the source modules that you've changed since they were last compiled.  The TADS 3 "make" facility is similar to the Unix "make" program, but is not programmable like the Unix version; instead, the TADS 3 version is pre-programmed with the relationships among source, object, and image files.

 

To build a program using TADS 3, you run the "make" command, t3make, with all of the source modules listed as arguments.  You do not need to use #include to combine the source modules from a master source file as you did in previous versions; instead, the linker combines the modules together for you.  (You can still use #include to structure your program as a single logical module if you want, but if you do so you will not gain the advantages of separate compilation.)

 

t3make first compiles each source file that has been modified since it was last compiled.  The result of compiling a source module is an "object file"; a source file called "mygame.t" would have a corresponding object file called "mygame.t3o" – "t3o" stands for "T3 Object."  If a source file or one of its included files has been modified since the corresponding object file was created, t3make compiles the source file; if the object file is up to date with the source, there's no need to recompile the source file, so t3make automatically skips it.

 

Note that the "object" in "object file" doesn't mean a code object, as in an instance of a class.  "Object file" is a term that most compiled languages use to refer to the output of the compiler, which is also the input to the linker.  An object file contains a version of the source program that has been translated into machine code, but which usually contains references to external symbols that must be resolved by the linker before the code becomes an executable program.

 

After compiling all of the modified source files into object files, t3make looks to see if the image file is up to date by checking to see if any object files are newer.  If at least one object file is newer than the image file, the image file must be re-linked.  (Thus, if any source files are compiled during this run of t3make, the image will obviously have to be re-linked.)  If it is necessary to re-link, t3make loads all of the object files, resolves their mutual external references, and creates an image file.

 

The biggest advantage of separate compilation is that loading an object file into the linker is usually much faster than compiling a source file.  If you're working on a large game, you will often make changes in only a few parts at a time, which means that only a small number of your source files might need to be compiled for each build.  This can often speed up building quite substantially.

 

The t3make command line looks like this:

 

   t3make –o mygame.t3 –Fo obj –Fy sym game1.t game2.t game3.t

 

The -o option tells the linker the name of the image file; this is the T3 executable file that you run using the TADS 3 interpreter.  The -Fo option, if you include it, tells the compiler where to put object files; by default, they'll go in the same place as the source file, but if you want to store object files in a separate directory, which you might wish to do to keep your source directory uncluttered, you can use this option.  Similarly, the -Fy option tells the compiler where to put "symbol" files, which are another kind of generated file that the compiler creates; as with objects, you might want to keep these files in a separate directory from the source to avoid clutter.  (You don't otherwise need to worry about symbol files; they're purely for the compiler's use.)  Finally, you must list the source files that make up your game.

 

Compile command syntax

The general syntax of the t3make command is as follows:

 

                    t3make [options] sources [-res resourceOptions]

 

The options are special keywords that all start with a dash ("-") that control the way the compiler works; more on these in a moment.  The sources are the names of the source files and library files that go into the build.  The resourceOptions let you add multi-media resources into the final image file.

 

Options

Here's a list of the compiler options that you can specify:

 

 

Source and library files

After the options, you list all of the source modules involved in the compilation.  Each source module can be a regular source file (a ".t" file), or it can be a "library" file, as explained below.  The compiler attempts to infer the type of each file by checking its filename suffix: if the suffix is ".tl", the compiler assumes that the file is a library, otherwise it assumes it is a source file.  If you don't use the conventional filename suffixes for your source and library files, you must explicitly tell the compiler the type each file by prefixing each file with a "-source" or "-lib" specifier.  For example:

 

  t3make -o mygame.t3 -source game1.tads -lib mylib.tadslib

 

Immediately following the name of a library, you can list one or more "-x" options to exclude modules that the library includes.  This is explained below.

 

The "-source" and "-lib" type specifiers are not actually options, even though they look like options (in that they start with a dash).  These specifiers can't be mixed in with the regular options; they can only appear in the module list portion of the command line, which follows all of the options.

 

The compiler treats the "-" prefix as special everywhere in the command line.  This means that if the name of one of your source files actually starts with "-", you must put a "-source" specifier immediately before that filename, even if it ends with the conventional suffix, because otherwise the compiler would be confused into thinking the filename was meant as a type specifier.

 

Note that you can always use the "-source" and "-lib" specifiers, even when you don't need to.  If you're putting your build settings into a project makefile (a .t3m file), it's a good idea to use "-source" and "-lib" specifiers consistently for all of your sources, because it makes the project file easier to read.  Also, when you use one a "-source" or "-lib" specifier, you can omit the ".t" or ".tl" suffix from the filename, because the compiler will know to automatically add the appropriate suffix using the appropriate local OS conventions.  So using the specifier and omitting the suffix makes your project files more portable by eliminating any dependency on OS file naming conventions.
 

Source Filename Uniqueness

All source files in a project must have unique "root" filenames.  (The root filename is the part of the filename that doesn't include any directory path prefix.)  This applies even to source files in libraries—a source file in one library can't have the same root filename as a source file in any other library, and it can't have the same root name as any source file included in the project's main source file list.

 

The reason that source filenames must be unique is that the compiler uses the source name to determine the name of the object (.t3o) and symbol (.t3s) files generated from the source file.  Since all of these generated files for a given project are usually placed into a single output directory, if two source modules had the same filename, they'd both correspond to the same object file, so one module's object file would overwrite the other with the same name.  To avoid this problem, the compiler requires that each source file's name is unique.

 

Resources

Following the list of source and library files, you can specify a list of multi-media resource files to bundle into the image file.  Bundling multi-media resources makes your game more self-contained, because it stores these resources directly in the .t3 file rather than as separate files; this means you have to provide only the .t3 file to people playing your game.  Bundling also provides some measure of protection for your graphics and sound effects files from casual perusal by users.

 

You can specify the list of multi-media resources to bundle into your image file by placing the "-res" option after all of your source and library modules, then listing your multi-media resources.  After the "-res" option, you can specify one or more resource item.  Each resource item can be one of the following:

 

 

Note that the "resource name" for a resource is the name by which the resource is known at run-time.  This is the name you use to access the resource from HTML; for example, it's the name you'd use in an <IMG SRC=xxx> tag.  You'd also use this name to access the resource from the File class.

 

There are two special things to note about resource bundling.

 

First, the compiler ignores the resource list when compiling for debugging.  When you're compiling for debugging, you usually don't intend to distribute the resulting image file; it's usually for your own use only.  Since you already have a copy of all of your resource files (after all, you need them in order to bundle them), and since you usually don't distribute a debugging image, there's no advantage to bundling the resources.  Plus, there's a disadvantage, which is that the bundling step adds time to the build; you usually want debug builds to finish as quickly as possible.

 

Second, the resources are added to the final image file after the pre-initialization phase has finished.  This means that you can include resources that are created during your pre-initialization phase, which is handy for things like the "gameinfo.txt" file (which the standard library builds automatically during pre-initialization).  This also means that resources you access during pre-initialization won't come from the image file, but this should be transparent to your program, since the resource loader automatically looks for the resource files in the file system anyway.

Libraries

It's often useful to take a set of source files and group them together as though they were a unit.  For example, the standard Adventure Library included with TADS consists of a number of separate source files; for the most part, you don't want to have to think about which individual files are involved - you just want to include the entire Adventure Library as a unit.

 

The compiler has a mechanism to simplify working with groups of files like this: you can create a separate, special kind of file that lists the regular source files that make up the group, and then include only this special listing file in your compilation.  The compiler automatically reads the list of files from the special file.  This special file is called a "library file" (not to be confused with the more generic usage of "library" that refers to a group of files the provide reusable source code, such as the Adventure Library - a library file is a specially formatted file that specifies a group of source files to include in a compilation).

 

By convention, a library filename always has the suffix ".tl" (but note that the period might be replaced with another character on some platforms, and some platforms don't use filename suffixes at all).

 

Library Headers (#include files)

The compiler automatically adds each library’s directory to the include path.  If you’re distributing a library, this means that you can bundle all of your library’s files together into a single directory, and the user will only have to specify the path to your library’s install directory once, when listing the .tl file on the compiler command line.

 

Library Format

A library file is simply a text file that obeys a specific format.  A library file is formatted with one definition per line (blank lines are ignored, as are lines starting with the comment character "#").  A definition has this format:

 

keyword: value

 

The "keyword" is a word specifying what kind of information the line contains, and the "value" is the text of the information defined.  The valid keywords are:

 

 

In addition, certain directives can appear as simply keywords without associated values:

 

 

Specifying Filenames

The values for the source and library keywords are filenames, given in a portable format.  You must not specify a filename extension (such as ".t") on these values, because the compiler adds the appropriate extension for the local platform automatically.  You must use slash ("/") characters to indicate directory path separators; the compiler automatically converts the "/" characters to the appropriate local conventions.  If you specify any directory paths, you must specify only relative paths that are subdirectories of the folder containing the library; you are not allowed to specify absolute paths, and you are not allowed to use ".." or other OS-specific conventions to refer to parent directories.  If you follow all of these conventions, you will ensure that your library files will be portable to all operating systems, so that other people using different operating systems won't have to modify your library files for their local conventions.

 

When the compiler reads a library file, it converts each source and library value to local conventions.  The compiler makes the following changes to each name:

 

 

Preprocessor Symbol (Macro) Substitution

Inside a library file, you can substitute the value of a preprocessor symbol defined with the -D option.  This allows you to create a library that selects different sub-sections that depend on -D settings; the standard adv3 library, for example, uses this to select the language-specific library based on the setting of the LANGUAGE symbol.

 

To substitute the value of a preprocessor symbol, use the format "$(NAME)", where NAME is the name of the preprocessor symbol to substitute.  For example, to substitute the value of the LANGUAGE variable, put "$(LANGUAGE)" where you want the value to be substituted:

 

library: $(LANGUAGE)/$(LANGUAGE).tl

 

In this example, the LANGUAGE variable's value is substituted into this line of text twice.  If the command line contained the option "-D LANGUAGE=en_us", then the compiler would read the line above as though it were written like this:

 

library: en_us/en_us.tl

 

The compiler substitutes all "$(NAME)" sequences before interpreting each line of text from the library file.

 

If a dollar sign appears in a library file, but isn't part of a complete "$(NAME)" sequence, the compiler displays an error message.  To use a dollar sign literally in a library file (for example, if you want to refer to a file that has a dollar sign in its name), write two dollar signs: the compiler converts each "$$" sequence to a single dollar sign.

 

Any symbols used in "$(NAME)" constructions must be defined with -D options, either on the command line or in the project (.t3m) file, before the library is listed in the command line or project file.  The compiler displays an error message if any symbol is used in a library file but isn't defined with a -D option.

 

Example

Here's an example of a library file:

 

name: Calculator Library

source: display

source: keypad

source: arith

source: sci

source: trig

 

This library has the display name "Calculator Library", and includes five source files: display.t, keypad.t, arith.t, sci.t, and trig.t.  Assuming you called this library "calc.tl", you would include the library in a compilation like so:

 

  t3make -d -lib calc.tl mygame.t

 

Because the library name ends in ".tl", you don't need to include a "-lib" specifier before it - the compiler infers that the file is a library from the suffix.  (You always can include a specifier for any file, but you don't need to unless the file has a non-standard suffix for its type.)

 

Exclusions

Sometimes, a library includes non-essential files that you don't need in your program.  For example, a library might have some extended functionality that it includes for those programs that want it, but which can be omitted by programs that don't use it.  Including extraneous code is generally not harmful, but it does unnecessarily increase the size of your program's compiled image file and its run-time memory needs, so it's best to avoid adding code you're not using.

 

The obvious way of eliminating unneeded code would be to edit the library file itself to remove the modules you don't need.  This isn't ideal, though, because it would remove those modules from other projects you're working on that might need the extra code.  To solve this problem, you could simply create a copy of the library file and remove the unneeded modules from it.  This creates another problem, though: if you got the library file from someone else, and they later change the library by renaming a source file or adding or removing files, you'd have to make the same changes to your copy or copies of the library.

 

Fortunately, there's a way to exclude files from a library without changing the library itself.  When you include a library on the compiler command line, the compiler lets you list one or more files to exclude, using "-x" specifiers.  A "-x" specifier must be placed on the compiler command line immediately after the library to which it applies.  Each "-x" is followed by the name of a source module to exclude, using the name as it appears in the library - that is, using the portable name format, without an extension and with "/" as the path separator.  Simply use the exact text of the source value as it appears in the library file.

 

Note that you cannot exclude an entire sub-library from a library.  If a library includes a sub-library, you must exclude the files from the sub-library individually.  To do so, treat the sub-library name as a path prefix, and place a "/" after the sub-library name, then add the filename as it appears in the sub-library.  For example, suppose that another library, desk.tl, includes the calc.tl library as a sub-library:

 

# desk accessory library
name: Desk Accessory Library
library: pen
library: pencil
library: calc

 

Now, suppose you compile a program including the desk.tl library, but you want to exclude the trig.t module included in the calc.tl library.  To do this, you'd write a "-x" option like this:

 

  t3make desk.tl -x calc/trig

Project Files

If you take advantage of the separate compilation capability by dividing your program into several source files, you might find that your t3make command lines become quite lengthy.  This will be of no concern if you're using an integrated environment such as TADS Workbench, but could become inconvenient if you're building directly from a command shell.

 

To make things easier for command-line users, t3make can read build commands from a "project file" (also known as a "makefile," after the Unix "make" utility, although TADS doesn't use the same command language as the Unix "make" tool).  A project file is simply a text file that contains the same information you'd normally put on the command line.  Each line of a project file can include one or more module names and options, separated by spaces.  Use a pound sign ("#") to start a comment; everything after a pound sign is ignored.  If you want to use spaces or pound signs within a filename or option, enclose the entire option in double quotes; if you want to use a double quote mark within a quoted option, use two double quote marks.

 

Here's a sample project file.

 

#

# project file for calc
#
 
# source files
"calc sources\calc.t"
"tok sources\tok.t"
 
# image file
-o "exe\calc.t3o"

 

To read options from a file, use the –f compiler option:

 

t3make –f calc.t3m

 

The options read from an options file are appended after the options on the command line, so you can mix options from a file and the command line.  For example, if you wanted to use the project file above to compile the program for debugging, you could enter a command like this:

 

t3make –f calc.t3m -d

Default Project File

If you run t3make and do not specify any modules (in other words, your command line consists only of options), and you also don't include a –f option to specify a project file, the compiler looks for a default project file called "makefile.t3m" in the current directory.  If this file is present, the compiler reads the file as though you had specified it with the –f option.  This makes it very easy to build your program; if you put your build options in the file makefile.t3m, you can build simply by typing "t3make".

 

The normal rules for project files apply to makefile.t3m, so you can specify additional options on the command line when building with the default project file.  For example, to build for debugging using the default project file, you would simply enter this:

 

t3make –d

Default Modules

TADS 3 includes a default source module called _main.t that contains some low-level support code that most programs need.  Because most programs will not have any reason to customize this module, the compiler automatically includes the module; this saves you a little work, because you don't have to add the module to your t3make command line explicitly.

 

However, it is possible that you'll want to use a different version of the code in _main.t, in which case you will need to remove the default version from the build.  To do this, use the compiler's –nodef option.  This option tells the compiler not to include any default modules in the build; only the modules you explicitly list on the command line (or in a project file) are included in the build in this case.

Default Search Paths

Include files: The compiler automatically adds the default system header directory to the end of the include file search path.  In effect, the compiler adds a -I option, after all user-specified -I options, specifying the system header directory.  The location of the system header directory varies by system; on DOS and Windows, this is the directory containing the compiler.  Refer to your system-specific release notes for details on other systems.

 

Source files:  The compiler automatically adds the default system library source directory to the end of the source file search path.  In effect, the compiler adds a -Fs option, after all user-specified -Fs options, specifying the system library source directory.  The location of this directory varies by system; on DOS and Windows, this is the compiler install directory.  Refer to your system-specific release notes for details on other systems.

Dependency Tracking

The t3make utility will compile a source file if it finds any of the following conditions:

 

 

In addition, t3make will re-link the image (.t3) file under any of these conditions:

 

 

The dependency tracking mechanism isn't perfect, and it can be fooled under certain circumstances:

 

 

You can force a full recompilation with the t3make –r option; you can use this option if you encounter any situations where you suspect that t3make is missing some dependency and therefore failing to notice a file that requires recompilation.