Implement a Makefile for Inform.
[inform.git] / src / directs.c
diff --git a/src/directs.c b/src/directs.c
new file mode 100644 (file)
index 0000000..d3b7f32
--- /dev/null
@@ -0,0 +1,1225 @@
+/* ------------------------------------------------------------------------- */
+/*   "directs" : Directives (# commands)                                     */
+/*                                                                           */
+/* Copyright (c) Graham Nelson 1993 - 2018                                   */
+/*                                                                           */
+/* This file is part of Inform.                                              */
+/*                                                                           */
+/* Inform is free software: you can redistribute it and/or modify            */
+/* it under the terms of the GNU General Public License as published by      */
+/* the Free Software Foundation, either version 3 of the License, or         */
+/* (at your option) any later version.                                       */
+/*                                                                           */
+/* Inform is distributed in the hope that it will be useful,                 */
+/* but WITHOUT ANY WARRANTY; without even the implied warranty of            */
+/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the              */
+/* GNU General Public License for more details.                              */
+/*                                                                           */
+/* You should have received a copy of the GNU General Public License         */
+/* along with Inform. If not, see https://gnu.org/licenses/                  */
+/*                                                                           */
+/* ------------------------------------------------------------------------- */
+
+#include "header.h"
+
+int no_routines,                   /* Number of routines compiled so far     */
+    no_named_routines,             /* Number not embedded in objects         */
+    no_locals,                     /* Number of locals in current routine    */
+    no_termcs;                     /* Number of terminating characters       */
+int terminating_characters[32];
+
+brief_location routine_starts_line; /* Source code location where the current
+                                      routine starts.  (Useful for reporting
+                                      "unused variable" warnings on the start
+                                      line rather than the end line.)        */
+
+static int constant_made_yet;      /* Have any constants been defined yet?   */
+
+static int ifdef_stack[32], ifdef_sp;
+
+/* ------------------------------------------------------------------------- */
+
+static int ebf_error_recover(char *s1, char *s2)
+{
+    /* Display an "expected... but found..." error, then skim forward
+       to the next semicolon and return FALSE. This is such a common
+       case in parse_given_directive() that it's worth a utility
+       function. You will see many error paths that look like:
+          return ebf_error_recover(...);
+    */
+    ebf_error(s1, s2);
+    panic_mode_error_recovery();
+    return FALSE;
+}
+
+/* ------------------------------------------------------------------------- */
+
+extern int parse_given_directive(int internal_flag)
+{   /*  Internal_flag is FALSE if the directive is encountered normally,
+        TRUE if encountered with a # prefix inside a routine or object
+        definition.
+
+        Returns: FALSE if program continues, TRUE if end of file reached.    */
+
+    int *trace_level = NULL; int32 i, j, k, n, flag;
+    const char *constant_name;
+    debug_location_beginning beginning_debug_location;
+
+    switch(token_value)
+    {
+
+    /* --------------------------------------------------------------------- */
+    /*   Abbreviate "string1" ["string2" ...]                                */
+    /* --------------------------------------------------------------------- */
+
+    case ABBREVIATE_CODE:
+
+        do
+        {  get_next_token();
+           if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))
+               return FALSE;
+
+           /* Z-code has a 64-abbrev limit; Glulx doesn't. */
+           if (!glulx_mode && no_abbreviations==64)
+           {   error("All 64 abbreviations already declared");
+               panic_mode_error_recovery(); return FALSE;
+           }
+           if (no_abbreviations==MAX_ABBREVS)
+               memoryerror("MAX_ABBREVS", MAX_ABBREVS);
+
+           if (abbrevs_lookup_table_made)
+           {   error("All abbreviations must be declared together");
+               panic_mode_error_recovery(); return FALSE;
+           }
+           if (token_type != DQ_TT)
+               return ebf_error_recover("abbreviation string", token_text);
+           if (strlen(token_text)<2)
+           {   error_named("It's not worth abbreviating", token_text);
+               continue;
+           }
+           /* Abbreviation string with null must fit in a MAX_ABBREV_LENGTH
+              array. */
+           if (strlen(token_text)>=MAX_ABBREV_LENGTH)
+           {   error_named("Abbreviation too long", token_text);
+               continue;
+           }
+           make_abbreviation(token_text);
+        } while (TRUE);
+
+    /* --------------------------------------------------------------------- */
+    /*   Array arrayname array...                                            */
+    /* --------------------------------------------------------------------- */
+
+    case ARRAY_CODE: make_global(TRUE, FALSE); break;      /* See "tables.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Attribute newname [alias oldname]                                   */
+    /* --------------------------------------------------------------------- */
+
+    case ATTRIBUTE_CODE:
+        make_attribute(); break;                          /* See "objects.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Class classname ...                                                 */
+    /* --------------------------------------------------------------------- */
+
+    case CLASS_CODE: 
+        if (internal_flag)
+        {   error("Cannot nest #Class inside a routine or object");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        make_class(NULL);                                 /* See "objects.c" */
+        return FALSE;
+
+    /* --------------------------------------------------------------------- */
+    /*   Constant newname [[=] value] [, ...]                                */
+    /* --------------------------------------------------------------------- */
+
+    case CONSTANT_CODE:
+        constant_made_yet=TRUE;
+
+      ParseConstantSpec:
+        get_next_token(); i = token_value;
+        beginning_debug_location = get_token_location_beginning();
+
+        if ((token_type != SYMBOL_TT)
+            || (!(sflags[i] & (UNKNOWN_SFLAG + REDEFINABLE_SFLAG))))
+        {   discard_token_location(beginning_debug_location);
+            return ebf_error_recover("new constant name", token_text);
+        }
+
+        assign_symbol(i, 0, CONSTANT_T);
+        constant_name = token_text;
+
+        get_next_token();
+
+        if ((token_type == SEP_TT) && (token_value == COMMA_SEP))
+        {   if (debugfile_switch && !(sflags[i] & REDEFINABLE_SFLAG))
+            {   debug_file_printf("<constant>");
+                debug_file_printf("<identifier>%s</identifier>", constant_name);
+                write_debug_symbol_optional_backpatch(i);
+                write_debug_locations(get_token_location_end(beginning_debug_location));
+                debug_file_printf("</constant>");
+            }
+            goto ParseConstantSpec;
+        }
+
+        if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))
+        {   if (debugfile_switch && !(sflags[i] & REDEFINABLE_SFLAG))
+            {   debug_file_printf("<constant>");
+                debug_file_printf("<identifier>%s</identifier>", constant_name);
+                write_debug_symbol_optional_backpatch(i);
+                write_debug_locations(get_token_location_end(beginning_debug_location));
+                debug_file_printf("</constant>");
+            }
+            return FALSE;
+        }
+
+        if (!((token_type == SEP_TT) && (token_value == SETEQUALS_SEP)))
+            put_token_back();
+
+        {   assembly_operand AO = parse_expression(CONSTANT_CONTEXT);
+            if (AO.marker != 0)
+            {   assign_marked_symbol(i, AO.marker, AO.value,
+                    CONSTANT_T);
+                sflags[i] |= CHANGE_SFLAG;
+                if (i == grammar_version_symbol)
+                    error(
+                "Grammar__Version must be given an explicit constant value");
+            }
+            else
+            {   assign_symbol(i, AO.value, CONSTANT_T);
+                if (i == grammar_version_symbol)
+                {   if ((grammar_version_number != AO.value)
+                        && (no_fake_actions > 0))
+                        error(
+                "Once a fake action has been defined it is too late to \
+change the grammar version. (If you are using the library, move any \
+Fake_Action directives to a point after the inclusion of \"Parser\".)");
+                    grammar_version_number = AO.value;
+                }
+            }
+        }
+
+        if (debugfile_switch && !(sflags[i] & REDEFINABLE_SFLAG))
+        {   debug_file_printf("<constant>");
+            debug_file_printf("<identifier>%s</identifier>", constant_name);
+            write_debug_symbol_optional_backpatch(i);
+            write_debug_locations
+                (get_token_location_end(beginning_debug_location));
+            debug_file_printf("</constant>");
+        }
+
+        get_next_token();
+        if ((token_type == SEP_TT) && (token_value == COMMA_SEP))
+            goto ParseConstantSpec;
+        put_token_back();
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Default constantname integer                                        */
+    /* --------------------------------------------------------------------- */
+
+    case DEFAULT_CODE:
+        if (module_switch)
+        {   error("'Default' cannot be used in -M (Module) mode");
+            panic_mode_error_recovery(); return FALSE;
+        }
+
+        get_next_token();
+        if (token_type != SYMBOL_TT)
+            return ebf_error_recover("name", token_text);
+
+        i = -1;
+        if (sflags[token_value] & UNKNOWN_SFLAG)
+        {   i = token_value;
+            sflags[i] |= DEFCON_SFLAG;
+        }
+
+        get_next_token();
+        if (!((token_type == SEP_TT) && (token_value == SETEQUALS_SEP)))
+            put_token_back();
+
+        {   assembly_operand AO;
+            AO = parse_expression(CONSTANT_CONTEXT);
+            if (i != -1)
+            {   if (AO.marker != 0)
+                {   assign_marked_symbol(i, AO.marker, AO.value,
+                        CONSTANT_T);
+                    sflags[i] |= CHANGE_SFLAG;
+                }
+                else assign_symbol(i, AO.value, CONSTANT_T);
+            }
+        }
+
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Dictionary 'word'                                                   */
+    /*   Dictionary 'word' val1                                              */
+    /*   Dictionary 'word' val1 val3                                         */
+    /* --------------------------------------------------------------------- */
+
+    case DICTIONARY_CODE:
+        /* In Inform 5, this directive had the form
+             Dictionary SYMBOL "word";
+           This was deprecated as of I6 (if not earlier), and is no longer
+           supported at all. The current form just creates a dictionary word,
+           with the given values for dict_par1 and dict_par3. If the word
+           already exists, the values are bit-or'd in with the existing
+           values.
+           (We don't offer a way to set dict_par2, because that is entirely
+           reserved for the verb number. Or'ing values into it would create
+           garbage.)
+         */
+        get_next_token();
+        if (token_type != SQ_TT && token_type != DQ_TT)
+            return ebf_error_recover("dictionary word", token_text);
+
+        {
+            char *wd = token_text;
+            int val1 = 0;
+            int val3 = 0;
+
+            get_next_token();
+            if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)) {
+                put_token_back();
+            }
+            else {
+                assembly_operand AO;
+                put_token_back();
+                AO = parse_expression(CONSTANT_CONTEXT);
+                if (module_switch && (AO.marker != 0))
+                    error("A definite value must be given as a Dictionary flag");
+                else
+                    val1 = AO.value;
+
+                get_next_token();
+                if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)) {
+                    put_token_back();
+                }
+                else {
+                    assembly_operand AO;
+                    put_token_back();
+                    AO = parse_expression(CONSTANT_CONTEXT);
+                    if (module_switch && (AO.marker != 0))
+                        error("A definite value must be given as a Dictionary flag");
+                    else
+                        val3 = AO.value;
+                }
+            }
+
+            if (!glulx_mode) {
+                if ((val1 & ~0xFF) || (val3 & ~0xFF)) {
+                    warning("Dictionary flag values cannot exceed $FF in Z-code");
+                }
+            }
+            else {
+                if ((val1 & ~0xFFFF) || (val3 & ~0xFFFF)) {
+                    warning("Dictionary flag values cannot exceed $FFFF in Glulx");
+                }
+            }
+
+            dictionary_add(wd, val1, 0, val3);
+        }
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   End                                                                 */
+    /* --------------------------------------------------------------------- */
+
+    case END_CODE: return(TRUE);
+
+    case ENDIF_CODE:
+        if (ifdef_sp == 0) error("'Endif' without matching 'If...'");
+        else ifdef_sp--;
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Extend ...                                                          */
+    /* --------------------------------------------------------------------- */
+
+    case EXTEND_CODE: extend_verb(); return FALSE;         /* see "tables.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Fake_Action name                                                    */
+    /* --------------------------------------------------------------------- */
+
+    case FAKE_ACTION_CODE:
+        make_fake_action(); break;                          /* see "verbs.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Global variable [= value / array...]                                */
+    /* --------------------------------------------------------------------- */
+
+    case GLOBAL_CODE: make_global(FALSE, FALSE); break;    /* See "tables.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   If...                                                               */
+    /*                                                                       */
+    /*   Note that each time Inform tests an If... condition, it stacks the  */
+    /*   result (TRUE or FALSE) on ifdef_stack: thus, the top of this stack  */
+    /*   reveals what clause of the current If... is being compiled:         */
+    /*                                                                       */
+    /*               If...;  ...  Ifnot;  ...  Endif;                        */
+    /*   top of stack:       TRUE        FALSE                               */
+    /*                                                                       */
+    /*   This is used to detect "two Ifnots in same If" errors.              */
+    /* --------------------------------------------------------------------- */
+
+    case IFDEF_CODE:
+        flag = TRUE;
+        goto DefCondition;
+    case IFNDEF_CODE:
+        flag = FALSE;
+
+      DefCondition:
+        get_next_token();
+        if (token_type != SYMBOL_TT)
+            return ebf_error_recover("symbol name", token_text);
+
+        if ((token_text[0] == 'V')
+            && (token_text[1] == 'N')
+            && (token_text[2] == '_')
+            && (strlen(token_text)==7))
+        {   i = atoi(token_text+3);
+            if (VNUMBER < i) flag = (flag)?FALSE:TRUE;
+            goto HashIfCondition;
+        }
+
+        if (sflags[token_value] & UNKNOWN_SFLAG) flag = (flag)?FALSE:TRUE;
+        else sflags[token_value] |= USED_SFLAG;
+        goto HashIfCondition;
+
+    case IFNOT_CODE:
+        if (ifdef_sp == 0)
+            error("'Ifnot' without matching 'If...'");
+        else
+        if (!(ifdef_stack[ifdef_sp-1]))
+            error("Second 'Ifnot' for the same 'If...' condition");
+        else
+        {   dont_enter_into_symbol_table = -2; n = 1;
+            directives.enabled = TRUE;
+            do
+            {   get_next_token();
+                if (token_type == EOF_TT)
+                {   error("End of file reached in code 'If...'d out");
+                    directives.enabled = FALSE;
+                    return TRUE;
+                }
+                if (token_type == DIRECTIVE_TT)
+                {   switch(token_value)
+                    {   case ENDIF_CODE:
+                            n--; break;
+                        case IFV3_CODE:
+                        case IFV5_CODE:
+                        case IFDEF_CODE:
+                        case IFNDEF_CODE:
+                        case IFTRUE_CODE:
+                        case IFFALSE_CODE:
+                            n++; break;
+                        case IFNOT_CODE:
+                            if (n == 1)
+                            {   error(
+                              "Second 'Ifnot' for the same 'If...' condition");
+                                break;
+                            }
+                    }
+                }
+            } while (n > 0);
+            ifdef_sp--; 
+            dont_enter_into_symbol_table = FALSE;
+            directives.enabled = FALSE;
+        }
+        break;
+
+    case IFV3_CODE:
+        flag = FALSE; if (version_number == 3) flag = TRUE;
+        goto HashIfCondition;
+
+    case IFV5_CODE:
+        flag = TRUE; if (version_number == 3) flag = FALSE;
+        goto HashIfCondition;
+
+    case IFTRUE_CODE:
+        {   assembly_operand AO;
+            AO = parse_expression(CONSTANT_CONTEXT);
+            if (module_switch && (AO.marker != 0))
+            {   error("This condition can't be determined");
+                flag = 0;
+            }
+            else flag = (AO.value != 0);
+        }
+        goto HashIfCondition;
+
+    case IFFALSE_CODE:
+        {   assembly_operand AO;
+            AO = parse_expression(CONSTANT_CONTEXT);
+            if (module_switch && (AO.marker != 0))
+            {   error("This condition can't be determined");
+                flag = 1;
+            }
+            else flag = (AO.value == 0);
+        }
+        goto HashIfCondition;
+
+    HashIfCondition:
+        get_next_token();
+        if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)))
+            return ebf_error_recover("semicolon after 'If...' condition", token_text);
+
+        if (flag)
+        {   ifdef_stack[ifdef_sp++] = TRUE; return FALSE; }
+        else
+        {   dont_enter_into_symbol_table = -2; n = 1;
+            directives.enabled = TRUE;
+            do
+            {   get_next_token();
+                if (token_type == EOF_TT)
+                {   error("End of file reached in code 'If...'d out");
+                    directives.enabled = FALSE;
+                    return TRUE;
+                }
+                if (token_type == DIRECTIVE_TT)
+                {
+                    switch(token_value)
+                    {   case ENDIF_CODE:
+                            n--; break;
+                        case IFV3_CODE:
+                        case IFV5_CODE:
+                        case IFDEF_CODE:
+                        case IFNDEF_CODE:
+                        case IFTRUE_CODE:
+                        case IFFALSE_CODE:
+                            n++; break;
+                        case IFNOT_CODE:
+                            if (n == 1)
+                            {   ifdef_stack[ifdef_sp++] = FALSE;
+                                n--; break;
+                            }
+                    }
+                }
+            } while (n > 0);
+            directives.enabled = FALSE;
+            dont_enter_into_symbol_table = FALSE;
+        }
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Import global <varname> [, ...]                                     */
+    /*                                                                       */
+    /* (Further imported goods may be allowed later.)                        */
+    /* --------------------------------------------------------------------- */
+
+    case IMPORT_CODE:
+        if (!module_switch)
+        {   error("'Import' can only be used in -M (Module) mode");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        directives.enabled = TRUE;
+        do
+        {   get_next_token();
+            if ((token_type == DIRECTIVE_TT) && (token_value == GLOBAL_CODE))
+                 make_global(FALSE, TRUE);
+            else error_named("'Import' cannot import things of this type:",
+                 token_text);
+            get_next_token();
+        } while ((token_type == SEP_TT) && (token_value == COMMA_SEP));
+        put_token_back();
+        directives.enabled = FALSE;
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Include "[>]filename"                                               */
+    /*                                                                       */
+    /* The ">" character means to load the file from the same directory as   */
+    /* the current file, instead of relying on the include path.             */
+    /* --------------------------------------------------------------------- */
+
+    case INCLUDE_CODE:
+        get_next_token();
+        if (token_type != DQ_TT)
+            return ebf_error_recover("filename in double-quotes", token_text);
+
+        {   char *name = token_text;
+
+            get_next_token();
+            if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)))
+                ebf_error("semicolon ';' after Include filename", token_text);
+
+            if (strcmp(name, "language__") == 0)
+                 load_sourcefile(Language_Name, 0);
+            else if (name[0] == '>')
+                 load_sourcefile(name+1, 1);
+            else load_sourcefile(name, 0);
+            return FALSE;
+        }
+
+    /* --------------------------------------------------------------------- */
+    /*   Link "filename"                                                     */
+    /* --------------------------------------------------------------------- */
+
+    case LINK_CODE:
+        get_next_token();
+        if (token_type != DQ_TT)
+            return ebf_error_recover("filename in double-quotes", token_text);
+        link_module(token_text);                           /* See "linker.c" */
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Lowstring constantname "text of string"                             */
+    /* --------------------------------------------------------------------- */
+    /* Unlike most constant creations, these do not require backpatching:    */
+    /* the low strings always occupy a table at a fixed offset in the        */
+    /* Z-machine (after the abbreviations table has finished, at 0x100).     */
+    /* --------------------------------------------------------------------- */
+
+    case LOWSTRING_CODE:
+        if (module_switch)
+        {   error("'LowString' cannot be used in -M (Module) mode");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        get_next_token(); i = token_value;
+        if ((token_type != SYMBOL_TT) || (!(sflags[i] & UNKNOWN_SFLAG)))
+            return ebf_error_recover("new low string name", token_text);
+
+        get_next_token();
+        if (token_type != DQ_TT)
+            return ebf_error_recover("literal string in double-quotes", token_text);
+
+        assign_symbol(i, compile_string(token_text, TRUE, TRUE), CONSTANT_T);
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Message | "information"                                             */
+    /*           | error "error message"                                     */
+    /*           | fatalerror "fatal error message"                          */
+    /*           | warning "warning message"                                 */
+    /* --------------------------------------------------------------------- */
+
+    case MESSAGE_CODE:
+        directive_keywords.enabled = TRUE;
+        get_next_token();
+        directive_keywords.enabled = FALSE;
+        if (token_type == DQ_TT)
+        {   int i;
+            if (hash_printed_since_newline) printf("\n");
+            for (i=0; token_text[i]!=0; i++)
+            {   if (token_text[i] == '^') printf("\n");
+                else
+                if (token_text[i] == '~') printf("\"");
+                else printf("%c", token_text[i]);
+            }
+            printf("\n");
+            break;
+        }
+        if ((token_type == DIR_KEYWORD_TT) && (token_value == ERROR_DK))
+        {   get_next_token();
+            if (token_type != DQ_TT)
+            {   return ebf_error_recover("error message in double-quotes", token_text);
+            }
+            error(token_text); break;
+        }
+        if ((token_type == DIR_KEYWORD_TT) && (token_value == FATALERROR_DK))
+        {   get_next_token();
+            if (token_type != DQ_TT)
+            {   return ebf_error_recover("fatal error message in double-quotes", token_text);
+            }
+            fatalerror(token_text); break;
+        }
+        if ((token_type == DIR_KEYWORD_TT) && (token_value == WARNING_DK))
+        {   get_next_token();
+            if (token_type != DQ_TT)
+            {   return ebf_error_recover("warning message in double-quotes", token_text);
+            }
+            warning(token_text); break;
+        }
+        return ebf_error_recover("a message in double-quotes, 'error', 'fatalerror' or 'warning'",
+            token_text);
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Nearby objname "short name" ...                                     */
+    /* --------------------------------------------------------------------- */
+
+    case NEARBY_CODE:
+        if (internal_flag)
+        {   error("Cannot nest #Nearby inside a routine or object");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        make_object(TRUE, NULL, -1, -1, -1);
+        return FALSE;                                     /* See "objects.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Object objname "short name" ...                                     */
+    /* --------------------------------------------------------------------- */
+
+    case OBJECT_CODE:
+        if (internal_flag)
+        {   error("Cannot nest #Object inside a routine or object");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        make_object(FALSE, NULL, -1, -1, -1);
+        return FALSE;                                     /* See "objects.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Origsource <file>                                                   */
+    /*   Origsource <file> <line>                                            */
+    /*   Origsource <file> <line> <char>                                     */
+    /*   Origsource                                                          */
+    /*                                                                       */
+    /*   The first three forms declare that all following lines are derived  */
+    /*   from the named Inform 7 source file (with an optional line number   */
+    /*   and character number). This will be reported in error messages and  */
+    /*   in debug output. The declaration holds through the next Origsource  */
+    /*   directive (but does not apply to included files).                   */
+    /*                                                                       */
+    /*   The fourth form, with no arguments, clears the declaration.         */
+    /*                                                                       */
+    /*   Unlike the Include directive, Origsource does not open the named    */
+    /*   file or even verify that it exists. The filename is treated as an   */
+    /*   opaque string.                                                      */
+    /* --------------------------------------------------------------------- */
+
+    case ORIGSOURCE_CODE:
+        {
+            char *origsource_file = NULL;
+            int32 origsource_line = 0;
+            int32 origsource_char = 0;
+
+            /* Parse some optional tokens followed by a mandatory semicolon. */
+
+            get_next_token();
+            if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))) {
+                if (token_type != DQ_TT) {
+                    return ebf_error_recover("a file name in double-quotes",
+                        token_text);
+                }
+                origsource_file = token_text;
+
+                get_next_token();
+                if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))) {
+                    if (token_type != NUMBER_TT) {
+                        return ebf_error_recover("a file line number",
+                            token_text);
+                    }
+                    origsource_line = token_value;
+                    if (origsource_line < 0)
+                        origsource_line = 0;
+
+                    get_next_token();
+                    if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))) {
+                        if (token_type != NUMBER_TT) {
+                            return ebf_error_recover("a file line number",
+                                token_text);
+                        }
+                        origsource_char = token_value;
+                        if (origsource_char < 0)
+                            origsource_char = 0;
+                        
+                        get_next_token();
+                    }
+                }
+            }
+
+            put_token_back();
+
+            set_origsource_location(origsource_file, origsource_line, origsource_char);
+        }
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Property [long] [additive] name [alias oldname]                     */
+    /* --------------------------------------------------------------------- */
+
+    case PROPERTY_CODE: make_property(); break;           /* See "objects.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Release <number>                                                    */
+    /* --------------------------------------------------------------------- */
+
+    case RELEASE_CODE:
+        {   assembly_operand AO;
+            AO = parse_expression(CONSTANT_CONTEXT);
+            if (module_switch && (AO.marker != 0))
+                error("A definite value must be given as release number");
+            else
+                release_number = AO.value;
+        }
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Replace routine [routinename]                                       */
+    /* --------------------------------------------------------------------- */
+
+    case REPLACE_CODE:
+        /* You can also replace system functions normally implemented in     */
+        /* the "hardware" of the Z-machine, like "random()":                 */
+
+        system_functions.enabled = TRUE;
+        directives.enabled = FALSE;
+        directive_keywords.enabled = FALSE;
+
+        /* Don't count the upcoming symbol as a top-level reference
+           *to* the function. */
+        df_dont_note_global_symbols = TRUE;
+        get_next_token();
+        df_dont_note_global_symbols = FALSE;
+        if (token_type == SYSFUN_TT)
+        {   if (system_function_usage[token_value] == 1)
+                error("You can't 'Replace' a system function already used");
+            else system_function_usage[token_value] = 2;
+            get_next_token();
+            if (!((token_type == SEP_TT) && (token_value == SEMICOLON_SEP)))
+            {
+                error("You can't give a 'Replace'd system function a new name");
+                panic_mode_error_recovery(); return FALSE;
+            }
+            return FALSE;
+        }
+
+        if (token_type != SYMBOL_TT)
+            return ebf_error_recover("name of routine to replace", token_text);
+        if (!(sflags[token_value] & UNKNOWN_SFLAG))
+            return ebf_error_recover("name of routine not yet defined", token_text);
+
+        sflags[token_value] |= REPLACE_SFLAG;
+
+        /* If a second symbol is provided, it will refer to the
+           original (replaced) definition of the routine. */
+        i = token_value;
+
+        system_functions.enabled = FALSE;
+        df_dont_note_global_symbols = TRUE;
+        get_next_token();
+        df_dont_note_global_symbols = FALSE;
+        if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))
+        {   return FALSE;
+        }
+
+        if (token_type != SYMBOL_TT || !(sflags[token_value] & UNKNOWN_SFLAG))
+            return ebf_error_recover("semicolon ';' or new routine name", token_text);
+
+        /* Define the original-form symbol as a zero constant. Its
+           value will be overwritten later, when we define the
+           replacement. */
+        assign_symbol(token_value, 0, CONSTANT_T);
+        add_symbol_replacement_mapping(i, token_value);
+
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Serial "yymmdd"                                                     */
+    /* --------------------------------------------------------------------- */
+
+    case SERIAL_CODE:
+        get_next_token();
+        if ((token_type != DQ_TT) || (strlen(token_text)!=6))
+        {   error("The serial number must be a 6-digit date in double-quotes");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        for (i=0; i<6; i++) if (isdigit(token_text[i])==0)
+        {   error("The serial number must be a 6-digit date in double-quotes");
+            panic_mode_error_recovery(); return FALSE;
+        }
+        strcpy(serial_code_buffer, token_text);
+        serial_code_given_in_program = TRUE;
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Statusline score/time                                               */
+    /* --------------------------------------------------------------------- */
+
+    case STATUSLINE_CODE:
+        if (module_switch)
+            warning("This does not set the final game's statusline");
+
+        directive_keywords.enabled = TRUE;
+        get_next_token();
+        directive_keywords.enabled = FALSE;
+        if ((token_type != DIR_KEYWORD_TT)
+            || ((token_value != SCORE_DK) && (token_value != TIME_DK)))
+            return ebf_error_recover("'score' or 'time' after 'statusline'", token_text);
+        if (token_value == SCORE_DK) statusline_flag = SCORE_STYLE;
+        else statusline_flag = TIME_STYLE;
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Stub routinename number-of-locals                                   */
+    /* --------------------------------------------------------------------- */
+
+    case STUB_CODE:
+        if (internal_flag)
+        {   error("Cannot nest #Stub inside a routine or object");
+            panic_mode_error_recovery(); return FALSE;
+        }
+
+        /* The upcoming symbol is a definition; don't count it as a
+           top-level reference *to* the stub function. */
+        df_dont_note_global_symbols = TRUE;
+        get_next_token();
+        df_dont_note_global_symbols = FALSE;
+        if (token_type != SYMBOL_TT)
+            return ebf_error_recover("routine name to stub", token_text);
+
+        i = token_value; flag = FALSE;
+
+        if (sflags[i] & UNKNOWN_SFLAG)
+        {   sflags[i] |= STUB_SFLAG;
+            flag = TRUE;
+        }
+
+        get_next_token(); k = token_value;
+        if (token_type != NUMBER_TT)
+            return ebf_error_recover("number of local variables", token_text);
+        if ((k>4) || (k<0))
+        {   error("Must specify 0 to 4 local variables for 'Stub' routine");
+            k = 0;
+        }
+
+        if (flag)
+        {
+            /*  Give these parameter-receiving local variables names
+                for the benefit of the debugging information file,
+                and for assembly tracing to look sensible.                   */
+
+            local_variable_texts[0] = "dummy1";
+            local_variable_texts[1] = "dummy2";
+            local_variable_texts[2] = "dummy3";
+            local_variable_texts[3] = "dummy4";
+
+            assign_symbol(i,
+                assemble_routine_header(k, FALSE, (char *) symbs[i], FALSE, i),
+                ROUTINE_T);
+
+            /*  Ensure the return value of a stubbed routine is false,
+                since this is necessary to make the library work properly    */
+
+            if (!glulx_mode)
+                assemblez_0(rfalse_zc);
+            else
+                assembleg_1(return_gc, zero_operand);
+
+            /*  Inhibit "local variable unused" warnings  */
+
+            for (i=1; i<=k; i++) variable_usage[i] = 1;
+            sequence_point_follows = FALSE;
+            assemble_routine_end(FALSE, get_token_locations());
+        }
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Switches switchblock                                                */
+    /* (this directive is ignored if the -i switch was set at command line)  */
+    /* --------------------------------------------------------------------- */
+
+    case SWITCHES_CODE:
+        dont_enter_into_symbol_table = TRUE;
+        get_next_token();
+        dont_enter_into_symbol_table = FALSE;
+        if (token_type != DQ_TT)
+            return ebf_error_recover("string of switches", token_text);
+        if (!ignore_switches_switch)
+        {   if (constant_made_yet)
+                error("A 'Switches' directive must must come before \
+the first constant definition");
+            switches(token_text, 0);                       /* see "inform.c" */
+        }
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   System_file                                                         */
+    /*                                                                       */
+    /* Some files are declared as "system files": this information is used   */
+    /* by Inform only to skip the definition of a routine X if the designer  */
+    /* has indicated his intention to Replace X.                             */
+    /* --------------------------------------------------------------------- */
+
+    case SYSTEM_CODE:
+        declare_systemfile(); break;                        /* see "files.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Trace dictionary                                                    */
+    /*         objects                                                       */
+    /*         symbols                                                       */
+    /*         verbs                                                         */
+    /*                      [on/off]                                         */
+    /*         assembly     [on/off]                                         */
+    /*         expressions  [on/off]                                         */
+    /*         lines        [on/off]                                         */
+    /* --------------------------------------------------------------------- */
+
+    case TRACE_CODE:
+        directives.enabled = FALSE;
+        trace_keywords.enabled = TRUE;
+        get_next_token();
+        trace_keywords.enabled = FALSE;
+        directives.enabled = TRUE;
+        if ((token_type == SEP_TT) && (token_value == SEMICOLON_SEP))
+        {   asm_trace_level = 1; return FALSE; }
+
+        if (token_type != TRACE_KEYWORD_TT)
+            return ebf_error_recover("debugging keyword", token_text);
+
+        trace_keywords.enabled = TRUE;
+
+        i = token_value; j = 0;
+        switch(i)
+        {   case DICTIONARY_TK: break;
+            case OBJECTS_TK:    break;
+            case VERBS_TK:      break;
+            default:
+                switch(token_value)
+                {   case ASSEMBLY_TK:
+                        trace_level = &asm_trace_level;  break;
+                    case EXPRESSIONS_TK:
+                        trace_level = &expr_trace_level; break;
+                    case LINES_TK:
+                        trace_level = &line_trace_level; break;
+                    case TOKENS_TK:
+                        trace_level = &tokens_trace_level; break;
+                    case LINKER_TK:
+                        trace_level = &linker_trace_level; break;
+                    case SYMBOLS_TK:
+                        trace_level = NULL; break;
+                    default:
+                        put_token_back();
+                        trace_level = &asm_trace_level; break;
+                }
+                j = 1;
+                get_next_token();
+                if ((token_type == SEP_TT) &&
+                    (token_value == SEMICOLON_SEP))
+                {   put_token_back(); break;
+                }
+                if (token_type == NUMBER_TT)
+                {   j = token_value; break; }
+                if ((token_type == TRACE_KEYWORD_TT) && (token_value == ON_TK))
+                {   j = 1; break; }
+                if ((token_type == TRACE_KEYWORD_TT) && (token_value == OFF_TK))
+                {   j = 0; break; }
+                put_token_back(); break;
+        }
+
+        switch(i)
+        {   case DICTIONARY_TK: show_dictionary();  break;
+            case OBJECTS_TK:    list_object_tree(); break;
+            case SYMBOLS_TK:    list_symbols(j);     break;
+            case VERBS_TK:      list_verb_table();  break;
+            default:
+                *trace_level = j;
+                break;
+        }
+        trace_keywords.enabled = FALSE;
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Undef symbol                                                        */
+    /* --------------------------------------------------------------------- */
+
+    case UNDEF_CODE:
+        get_next_token();
+        if (token_type != SYMBOL_TT)
+            return ebf_error_recover("symbol name", token_text);
+
+        if (sflags[token_value] & UNKNOWN_SFLAG)
+        {   break; /* undef'ing an undefined constant is okay */
+        }
+
+        if (stypes[token_value] != CONSTANT_T)
+        {   error_named("Cannot Undef a symbol which is not a defined constant:", (char *)symbs[token_value]);
+            break;
+        }
+
+        if (debugfile_switch)
+        {   write_debug_undef(token_value);
+        }
+        end_symbol_scope(token_value);
+        sflags[token_value] |= USED_SFLAG;
+        break;
+
+    /* --------------------------------------------------------------------- */
+    /*   Verb ...                                                            */
+    /* --------------------------------------------------------------------- */
+
+    case VERB_CODE: make_verb(); return FALSE;             /* see "tables.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Version <number>                                                    */
+    /* --------------------------------------------------------------------- */
+
+    case VERSION_CODE:
+
+        {   assembly_operand AO;
+            AO = parse_expression(CONSTANT_CONTEXT);
+            /* If a version has already been set on the command line,
+               that overrides this. */
+            if (version_set_switch)
+            {
+              warning("The Version directive was overridden by a command-line argument.");
+              break;
+            }
+
+            if (module_switch && (AO.marker != 0))
+                error("A definite value must be given as version number");
+            else 
+            if (glulx_mode) 
+            {
+              warning("The Version directive does not work in Glulx. Use \
+-vX.Y.Z instead, as either a command-line argument or a header comment.");
+              break;
+            }
+            else
+            {   i = AO.value;
+                if ((i<3) || (i>8))
+                {   error("The version number must be in the range 3 to 8");
+                    break;
+                }
+                select_version(i);
+            }
+        }
+        break;                                             /* see "inform.c" */
+
+    /* --------------------------------------------------------------------- */
+    /*   Zcharacter table <num> ...                                          */
+    /*   Zcharacter table + <num> ...                                        */
+    /*   Zcharacter <string> <string> <string>                               */
+    /*   Zcharacter <char>                                                   */
+    /* --------------------------------------------------------------------- */
+
+    case ZCHARACTER_CODE:
+
+        if (glulx_mode) {
+            error("The Zcharacter directive has no meaning in Glulx.");
+            panic_mode_error_recovery(); return FALSE;
+        }
+
+        directive_keywords.enabled = TRUE;
+        get_next_token();
+        directive_keywords.enabled = FALSE;
+
+        switch(token_type)
+        {   case DQ_TT:
+                new_alphabet(token_text, 0);
+                get_next_token();
+                if (token_type != DQ_TT)
+                    return ebf_error_recover("double-quoted alphabet string", token_text);
+                new_alphabet(token_text, 1);
+                get_next_token();
+                if (token_type != DQ_TT)
+                    return ebf_error_recover("double-quoted alphabet string", token_text);
+                new_alphabet(token_text, 2);
+            break;
+
+            case SQ_TT:
+                map_new_zchar(text_to_unicode(token_text));
+                if (token_text[textual_form_length] != 0)
+                    return ebf_error_recover("single character value", token_text);
+            break;
+
+            case DIR_KEYWORD_TT:
+            switch(token_value)
+            {   case TABLE_DK:
+                {   int plus_flag = FALSE;
+                    get_next_token();
+                    if ((token_type == SEP_TT) && (token_value == PLUS_SEP))
+                    {   plus_flag = TRUE;
+                        get_next_token();
+                    }
+                    while ((token_type!=SEP_TT) || (token_value!=SEMICOLON_SEP))
+                    {   switch(token_type)
+                        {   case NUMBER_TT:
+                                new_zscii_character(token_value, plus_flag);
+                                plus_flag = TRUE; break;
+                            case SQ_TT:
+                                new_zscii_character(text_to_unicode(token_text),
+                                    plus_flag);
+                                if (token_text[textual_form_length] != 0)
+                                    return ebf_error_recover("single character value",
+                                        token_text);
+                                plus_flag = TRUE;
+                                break;
+                            default:
+                                return ebf_error_recover("character or Unicode number",
+                                    token_text);
+                        }
+                        get_next_token();
+                    }
+                    if (plus_flag) new_zscii_finished();
+                    put_token_back();
+                }
+                    break;
+                case TERMINATING_DK:
+                    get_next_token();
+                    while ((token_type!=SEP_TT) || (token_value!=SEMICOLON_SEP))
+                    {   switch(token_type)
+                        {   case NUMBER_TT:
+                                terminating_characters[no_termcs++]
+                                    = token_value;
+                                break;
+                            default:
+                                return ebf_error_recover("ZSCII number",
+                                    token_text);
+                        }
+                        get_next_token();
+                    }
+                    put_token_back();
+                    break;
+                default:
+                    return ebf_error_recover("'table', 'terminating', \
+a string or a constant",
+                        token_text);
+            }
+                break;
+            default:
+                return ebf_error_recover("three alphabet strings, \
+a 'table' or 'terminating' command or a single character", token_text);
+        }
+        break;
+
+    /* ===================================================================== */
+
+    }
+
+    /* We are now at the end of a syntactically valid directive. It
+       should be terminated by a semicolon. */
+
+    get_next_token();
+    if ((token_type != SEP_TT) || (token_value != SEMICOLON_SEP))
+    {   ebf_error("';'", token_text);
+        /* Put the non-semicolon back. We will continue parsing from
+           that point, in hope that it's the start of a new directive.
+           (This recovers cleanly from a missing semicolon at the end
+           of a directive. It's not so clean if the directive *does*
+           end with a semicolon, but there's extra garbage before it.) */
+        put_token_back();
+    }
+    return FALSE;
+}
+
+/* ========================================================================= */
+/*   Data structure management routines                                      */
+/* ------------------------------------------------------------------------- */
+
+extern void init_directs_vars(void)
+{
+}
+
+extern void directs_begin_pass(void)
+{   no_routines = 0;
+    no_named_routines = 0;
+    no_locals = 0;
+    no_termcs = 0;
+    constant_made_yet = FALSE;
+    ifdef_sp = 0;
+}
+
+extern void directs_allocate_arrays(void)
+{
+}
+
+extern void directs_free_arrays(void)
+{
+}
+
+/* ========================================================================= */