To parse individual commands from Neptune files.


§1. The command set. Each different operation is defined with a block like so:

typedef struct kind_command_definition {
    char *text_of_command;
    int opcode_number;  one of the *_KCC values below
    int operand_type;  one of the *_KCA values below
} kind_command_definition;

§2. The operands have different types, and the possibilities are given here:

enum NO_KCA from 0    there's no operand
enum BOOLEAN_KCA      must be yes or no
enum CCM_KCA          a constant compilation method
enum TEXT_KCA         any text (no quotation marks or other delimiters are used)
enum VOCABULARY_KCA   any single word
enum NUMERIC_KCA      any decimal number
enum CONSTRUCTOR_KCA  any valid kind number, such as "number"
enum TEMPLATE_KCA     the name of a template whose definition is given in the file
enum MACRO_KCA        the name of a macro whose definition is given in the file

§3. And, to cut to the chase, here is the complete table of commands:

enum apply_macro_KCC from 1
enum invent_source_text_KCC
enum can_coincide_with_property_KCC
enum can_exchange_KCC
enum compatible_with_KCC
enum comparison_routine_KCC
enum comparison_schema_KCC
enum constant_compilation_method_KCC
enum default_value_KCC
enum distinguishing_routine_KCC
enum documentation_reference_KCC
enum explicit_GPR_identifier_KCC
enum heap_size_estimate_KCC
enum printing_routine_for_debugging_KCC
enum printing_routine_KCC
enum index_default_value_KCC
enum index_maximum_value_KCC
enum index_minimum_value_KCC
enum indexed_grey_if_empty_KCC
enum index_priority_KCC
enum conforms_to_KCC
enum is_incompletely_defined_KCC
enum loop_domain_schema_KCC
enum modifying_adjective_KCC
enum multiple_block_KCC
enum plural_KCC
enum recognition_routine_KCC
enum singular_KCC
enum specification_text_KCC
enum small_block_size_KCC
enum terms_KCC
kind_command_definition table_of_kind_commands[] = {
    { "can-coincide-with-property",     can_coincide_with_property_KCC,     BOOLEAN_KCA },
    { "can-exchange",                   can_exchange_KCC,                   BOOLEAN_KCA },
    { "indexed-grey-if-empty",          indexed_grey_if_empty_KCC,          BOOLEAN_KCA },
    { "is-incompletely-defined",        is_incompletely_defined_KCC,        BOOLEAN_KCA },
    { "multiple-block",                 multiple_block_KCC,                 BOOLEAN_KCA },

    { "constant-compilation-method",    constant_compilation_method_KCC,    CCM_KCA },

    { "comparison-routine",             comparison_routine_KCC,             TEXT_KCA },
    { "default-value",                  default_value_KCC,                  TEXT_KCA },
    { "distinguishing-routine",         distinguishing_routine_KCC,         TEXT_KCA },
    { "documentation-reference",        documentation_reference_KCC,        TEXT_KCA },
    { "parsing-routine",                explicit_GPR_identifier_KCC,        TEXT_KCA },
    { "printing-routine",               printing_routine_KCC,               TEXT_KCA },
    { "printing-routine-for-debugging", printing_routine_for_debugging_KCC, TEXT_KCA },
    { "index-default-value",            index_default_value_KCC,            TEXT_KCA },
    { "index-maximum-value",            index_maximum_value_KCC,            TEXT_KCA },
    { "index-minimum-value",            index_minimum_value_KCC,            TEXT_KCA },
    { "loop-domain-schema",             loop_domain_schema_KCC,             TEXT_KCA },
    { "recognition-routine",            recognition_routine_KCC,            TEXT_KCA },
    { "specification-text",             specification_text_KCC,             TEXT_KCA },

    { "comparison-schema",              comparison_schema_KCC,              CONSTRUCTOR_KCA },
    { "compatible-with",                compatible_with_KCC,                CONSTRUCTOR_KCA },
    { "conforms-to",                    conforms_to_KCC,                    CONSTRUCTOR_KCA },

    { "plural",                         plural_KCC,                         VOCABULARY_KCA },
    { "singular",                       singular_KCC,                       VOCABULARY_KCA },

    { "terms",                          terms_KCC,                          TEXT_KCA },
    { "heap-size-estimate",             heap_size_estimate_KCC,             NUMERIC_KCA },
    { "index-priority",                 index_priority_KCC,                 NUMERIC_KCA },
    { "small-block-size",               small_block_size_KCC,               NUMERIC_KCA },

    { "invent-source-text",             invent_source_text_KCC,             TEMPLATE_KCA },

    { "apply-macro",                    apply_macro_KCC,                    MACRO_KCA },

    { NULL, -1, NO_KCA }
};

§4. When processing a command, we parse it into one of the following structures:

typedef struct single_kind_command {
    struct kind_command_definition *which_kind_command;
    int boolean_argument;  where appropriate
    int numeric_argument;  where appropriate
    struct text_stream *textual_argument;  where appropriate
    int ccm_argument;  where appropriate
    struct word_assemblage vocabulary_argument;  where appropriate
    struct text_stream *constructor_argument;  where appropriate
    struct kind_template_definition *template_argument;  where appropriate
    struct kind_macro_definition *macro_argument;  where appropriate
    struct text_file_position *origin;
    struct kind_constructor *defined_for;
    int completed;
} single_kind_command;

§5. Parsing single kind commands. Each command is read in as text, parsed and stored into a modest structure.

kind_constructor *constructor_described = NULL;

single_kind_command NeptuneSyntax::parse_command(text_stream *whole_command,
    text_file_position *tfp) {
    single_kind_command stc;
    Initialise the STC to a blank command5.1;

    if (Str::eq(whole_command, I"}")) {
        if (StarTemplates::recording()) StarTemplates::end(whole_command, tfp);
        else if (NeptuneMacros::recording()) NeptuneMacros::end(tfp);
        else constructor_described = NULL;
        stc.completed = TRUE;
    } else if (StarTemplates::recording()) {
        StarTemplates::record_line(whole_command, tfp);
        stc.completed = TRUE;
    } else if (Str::get_last_char(whole_command) == '{') {
        if (constructor_described) {
            NeptuneFiles::error(whole_command,
                I"previous declaration not closed with '}'", tfp);
            constructor_described = NULL;
        }
        match_results mr = Regexp::create_mr();
        if (Regexp::match(&mr, whole_command, L"invention (%C+) {")) {
            StarTemplates::begin(mr.exp[0], tfp);
        } else if (Regexp::match(&mr, whole_command, L"macro (#%C+) {")) {
            NeptuneMacros::begin(mr.exp[0], tfp);
        } else if (Regexp::match(&mr, whole_command, L"(%C+) (%C+) (%C+) {")) {
            int should_know = NOT_APPLICABLE;
            if (Str::eq(mr.exp[0], I"new")) should_know = FALSE;
            else if (Str::eq(mr.exp[0], I"builtin")) should_know = TRUE;
            if (should_know == NOT_APPLICABLE)
                NeptuneFiles::error(whole_command,
                    I"declaration must begin 'new' or 'builtin'", tfp);
            else {
                int group = -1;
                if (Str::eq(mr.exp[1], I"punctuation")) group = PUNCTUATION_GRP;
                else if (Str::eq(mr.exp[1], I"protocol")) group = PROTOCOL_GRP;
                else if (Str::eq(mr.exp[1], I"base")) group = BASE_CONSTRUCTOR_GRP;
                else if (Str::eq(mr.exp[1], I"constructor")) group = PROPER_CONSTRUCTOR_GRP;
                if (group < 0)
                    NeptuneFiles::error(whole_command,
                        I"must declare 'variable', 'protocol', 'base' or 'constructor'", tfp);
                else {
                    text_stream *name = mr.exp[2];
                    Create a new constructor5.2;
                }
            }
        } else {
            NeptuneFiles::error(whole_command,
                I"malformed declaration line", tfp);
        }
        Regexp::dispose_of(&mr);
        stc.completed = TRUE;
    } else if (Str::get_last_char(whole_command) == ':') {
        NeptuneFiles::error(whole_command, I"trailing colon was unexpected", tfp);
        stc.completed = TRUE;
    } else {
        TEMPORARY_TEXT(command)
        TEMPORARY_TEXT(argument)

        Parse line into command and argument, divided by a colon5.3;

        Identify the command being used5.4;

        switch(stc.which_kind_command->operand_type) {
            case BOOLEAN_KCA: Parse a boolean argument for a kind command5.5; break;
            case CCM_KCA: Parse a CCM argument for a kind command5.6; break;
            case CONSTRUCTOR_KCA: Parse a constructor-name argument for a kind command5.10; break;
            case MACRO_KCA: Parse a macro name argument for a kind command5.12; break;
            case NUMERIC_KCA: Parse a numeric argument for a kind command5.9; break;
            case TEMPLATE_KCA: Parse a template name argument for a kind command5.11; break;
            case TEXT_KCA: Parse a textual argument for a kind command5.7; break;
            case VOCABULARY_KCA: Parse a vocabulary argument for a kind command5.8; break;
        }
        DISCARD_TEXT(command)
        DISCARD_TEXT(argument)
    }
    return stc;
}

§5.1. Initialise the STC to a blank command5.1 =

    stc.which_kind_command = NULL;
    stc.boolean_argument = NOT_APPLICABLE;
    stc.numeric_argument = 0;
    stc.textual_argument = Str::new();
    stc.ccm_argument = -1;
    stc.vocabulary_argument = WordAssemblages::lit_0();
    stc.constructor_argument = Str::new();
    stc.macro_argument = NULL;
    stc.template_argument = NULL;
    stc.completed = FALSE;
    stc.origin = tfp;
    stc.defined_for = constructor_described;

§5.2. Create a new constructor5.2 =

    int do_know = FamiliarKinds::is_known(name);
    if ((do_know == FALSE) && (should_know == TRUE))
        NeptuneFiles::error(whole_command, I"kind command describes kind with no known name", tfp);
    if ((do_know == TRUE) && (should_know == FALSE))
        NeptuneFiles::error(whole_command, I"kind command describes already-known kind", tfp);
    constructor_described =
        KindConstructors::new(Kinds::get_construct(K_value), name, NULL, group);
    #ifdef NEW_BASE_KINDS_CALLBACK
    if ((constructor_described != CON_KIND_VARIABLE) &&
        (constructor_described != CON_INTERMEDIATE)) {
        NEW_BASE_KINDS_CALLBACK(
            Kinds::base_construction(constructor_described), NULL, name, EMPTY_WORDING);
    }
    #endif

§5.3. Spaces and tabs after the colon are skipped; so a textual argument cannot begin with those characters, but that doesn't matter for the things we need.

Parse line into command and argument, divided by a colon5.3 =

    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, whole_command, L" *(%c+?) *: *(%c+?) *")) {
        Str::copy(command, mr.exp[0]);
        Str::copy(argument, mr.exp[1]);
        Regexp::dispose_of(&mr);
    } else {
        NeptuneFiles::error(whole_command, I"kind command without argument", tfp);
    }

§5.4. The following is clearly inefficient, but is not worth optimising. It makes about 20 string comparisons per command, and there are about 600 commands in a typical run of Inform, so the total cost is about 12,000 comparisons with quite small strings as arguments — which is negligible for our purposes, so we neglect it.

Identify the command being used5.4 =

    for (int i=0; table_of_kind_commands[i].text_of_command; i++)
        if (Str::eq_narrow_string(command, table_of_kind_commands[i].text_of_command))
            stc.which_kind_command = &(table_of_kind_commands[i]);

    if (stc.which_kind_command == NULL) {
        NeptuneFiles::error(command, I"no such kind command", tfp);
        stc.completed = TRUE; return stc;
    }

§5.5. Parse a boolean argument for a kind command5.5 =

    if (Str::eq_wide_string(argument, L"yes")) stc.boolean_argument = TRUE;
    else if (Str::eq_wide_string(argument, L"no")) stc.boolean_argument = FALSE;
    else NeptuneFiles::error(command, I"boolean kind command takes yes/no argument", tfp);

§5.6. Parse a CCM argument for a kind command5.6 =

    if (Str::eq_wide_string(argument, L"none")) stc.ccm_argument = NONE_CCM;
    else if (Str::eq_wide_string(argument, L"literal")) stc.ccm_argument = LITERAL_CCM;
    else if (Str::eq_wide_string(argument, L"quantitative")) stc.ccm_argument = NAMED_CONSTANT_CCM;
    else if (Str::eq_wide_string(argument, L"special")) stc.ccm_argument = SPECIAL_CCM;
    else {
        NeptuneFiles::error(command,
            I"kind command with unknown constant-compilation-method", tfp);
        stc.completed = TRUE; return stc;
    }

§5.7. Parse a textual argument for a kind command5.7 =

    Str::copy(stc.textual_argument, argument);

§5.8. Parse a vocabulary argument for a kind command5.8 =

    stc.vocabulary_argument = WordAssemblages::lit_0();
    feed_t id = Feeds::begin();
    Feeds::feed_text(argument);
    wording W = Feeds::end(id);
    if (Wordings::length(W) >= 30) {
        NeptuneFiles::error(command, I"too many words in kind command", tfp);
        stc.completed = TRUE; return stc;
    } else
        stc.vocabulary_argument = WordAssemblages::from_wording(W);

§5.9. Parse a numeric argument for a kind command5.9 =

    stc.numeric_argument = Str::atoi(argument, 0);

§5.10. Parse a constructor-name argument for a kind command5.10 =

    match_results mr = Regexp::create_mr();
    if (Regexp::match(&mr, argument, L"(%c*?)>>>(%c+)")) {
        Str::copy(argument, mr.exp[0]);
        Str::copy(stc.textual_argument, mr.exp[1]);
        Regexp::dispose_of(&mr);
    }
    stc.constructor_argument = Str::duplicate(argument);

§5.11. Parse a template name argument for a kind command5.11 =

    stc.template_argument = StarTemplates::parse_name(argument);
    if (stc.template_argument == NULL) {
        NeptuneFiles::error(command, I"unknown template name in kind command", tfp);
        stc.completed = TRUE; return stc;
    }

§5.12. Parse a macro name argument for a kind command5.12 =

    stc.macro_argument = NeptuneMacros::parse_name(argument);
    if (stc.macro_argument == NULL) {
        NeptuneFiles::error(command, I"unknown template name in kind command", tfp);
        stc.completed = TRUE; return stc;
    }