To manage prototype pieces of code for use in code-generation.


§1. Schemas. The calculus module should not in any direct way be involved in code generation; on the other hand, predicates and quantifiers need eventually to result in compiled code, and that code will look different for different predicates. There has to be some way to systematically handle those differences.

Code in the Inform compiler is generated largely from "schemas", which are small model pieces of code used with variations in different settings. There are two different data structures for these:

For inter schemas and how the conversion of (a) to (b) is done, see the Inform compiler source at Inter Schemas (in building). If this calculus module is used outside of Inform, of course, no inter_schema will exist.

A simple example of an i6_schema might use the notation *1 == *2; this will ultimately compile to a test that two quantities are numerically equal. As this example shows, * is an escape character. See Parsing Inter Schemas (in building); *1 is an example of what is called an "abbreviated command" there.

§2. The i6_schema structure is very simple, then. Schemas can be of unlimited length, but we want to be able to create and dispose of them quickly and to avoid unnecessary stream memory claims. So each i6_schema structure contains a fixed block of storage for the first few characters. (In fact, long ones are never needed in practice, but we must avoid any risk of buffer overrun for safety.)

define TYPICAL_I6_SCHEMA_LENGTH 128  in practice 40 is plenty
typedef struct i6_schema {
    inchar32_t prototype_storage[TYPICAL_I6_SCHEMA_LENGTH];  used just to make space for...
    struct text_stream prototype;  ...this
    int no_quoted_inames;
    #ifdef CORE_MODULE
    struct inter_schema *compiled;
    struct inter_name *quoted_inames[2];
    #endif
} i6_schema;

§3. Annotated schemas. It is sometimes convenient to carry around a schema together with calculus terms for what will go into *1 and *2 when it is expanded, and with a few other contextual details.

typedef struct annotated_i6_schema {
    struct i6_schema *schema;
    int negate_schema;  true if atom is to be tested with the opposite parity
    struct pcalc_term pt0;  terms on which the I6 schema is to be expanded
    struct pcalc_term pt1;
    int involves_action_variables;
} annotated_i6_schema;

§4. And here it is, before being annotated...

annotated_i6_schema Calculus::Schemas::blank_asch(void) {
    annotated_i6_schema asch;
    asch.schema = Calculus::Schemas::new(" ");
    asch.negate_schema = FALSE;
    asch.pt0 = Terms::new_variable(0);
    asch.pt1 = Terms::new_variable(0);
    asch.involves_action_variables = FALSE;
    return asch;
}

§5. Building schemas. When schemas are generated inside Inform, they often look as if they have an even more elaborate syntax, with escapes like %s in them. But this is because they are generated with the following printf-style function. Those % escapes are expanded now, when the schema is created, and not later when code is generated from it. For example, the function call:

Calculus::Schemas::new("*1.%n = *2.%n", X, Y)

might produce a schema whose prototype text came out as

*1.x100 = *2.y62

...supposing that x100 and y62 were the Inter identifiers for whatever was referred to by the inter_name values X and Y supplied in the arguments. Here then is that printf-like function:

int unique_qi_counter = 0;  quoted iname count

i6_schema *Calculus::Schemas::new(char *fmt, ...) {
    va_list ap;  the variable argument list signified by the dots
    i6_schema *sch = CREATE(i6_schema);
    sch->prototype = Streams::new_buffer(TYPICAL_I6_SCHEMA_LENGTH, sch->prototype_storage);
    sch->no_quoted_inames = 0;
    text_stream *OUT = &(sch->prototype);
    Process the varargs into schema prototype text5.3;
    va_end(ap);  macro to end variable argument processing
    #ifdef CORE_MODULE
    sch->compiled = ParsingSchemas::from_i6s(&(sch->prototype),
        sch->no_quoted_inames, (void **) sch->quoted_inames);
    #endif
    return sch;
}

§5.1. And this is a variation for modifying an existing schema:

void Calculus::Schemas::modify(i6_schema *sch, char *fmt, ...) {
    va_list ap;  the variable argument list signified by the dots
    sch->prototype = Streams::new_buffer(TYPICAL_I6_SCHEMA_LENGTH, sch->prototype_storage);
    sch->no_quoted_inames = 0;
    text_stream *OUT = &(sch->prototype);
    Process the varargs into schema prototype text5.3;
    va_end(ap);  macro to end variable argument processing
    #ifdef CORE_MODULE
    sch->compiled = ParsingSchemas::from_i6s(&(sch->prototype),
        sch->no_quoted_inames, (void **) sch->quoted_inames);
    #endif
}

§5.2. And another:

void Calculus::Schemas::append(i6_schema *sch, char *fmt, ...) {
    va_list ap;  the variable argument list signified by the dots
    text_stream *OUT = &(sch->prototype);
    Process the varargs into schema prototype text5.3;
    va_end(ap);  macro to end variable argument processing
    #ifdef CORE_MODULE
    sch->compiled = ParsingSchemas::from_i6s(&(sch->prototype),
        sch->no_quoted_inames, (void **) sch->quoted_inames);
    #endif
}

§5.3. Either way, the schema's prototype is written as follows:

Process the varargs into schema prototype text5.3 =

    char *p;
    va_start(ap, fmt);  macro to begin variable argument processing
    for (p = fmt; *p; p++) {
        switch (*p) {
            case '%': Recognise schema-format escape sequences5.3.1; break;
            default: PUT((inchar32_t) *p); break;
        }
    }

§5.3.1. We recognise only a few escapes here: %%, a literal percentage sign; %d, an integer; %s, a C string; %S, a text stream; and three which are higher-level:

Recognise schema-format escape sequences5.3.1 =

    p++;
    switch (*p) {
        case 'd': WRITE("%d", va_arg(ap, int)); break;
        case 'k':
            #ifdef CORE_MODULE
            RTKindIDs::write_weak_identifier(OUT, va_arg(ap, kind *));
            #endif
            break;
        case 'L':
            #ifdef CORE_MODULE
            WRITE("%~L", va_arg(ap, local_variable *)); break;
            #endif
            break;
        case 'n': {
            int N = sch->no_quoted_inames++;
            if (N >= 2) internal_error("too many inter_name quotes");
            #ifdef CORE_MODULE
            sch->quoted_inames[N] = (inter_name *) va_arg(ap, inter_name *);
            #endif
            WRITE("QUOTED_INAME_%d_%08x", N, unique_qi_counter++);
            break;
        }
        case 'N': WRITE("%N", va_arg(ap, int)); break;
        case 's': WRITE("%s", va_arg(ap, char *)); break;
        case 'S': WRITE("%S", va_arg(ap, text_stream *)); break;
        case '%': PUT('%'); break;
        default:
            fprintf(stderr, "*** Bad schema format: <%s> ***\n", fmt);
            internal_error("Unknown % string escape in schema format");
    }

§6. Emptiness. A schema is empty if its prototype is the empty text.

int Calculus::Schemas::empty(i6_schema *sch) {
    if (sch == NULL) return TRUE;
    if (Str::len(&(sch->prototype)) == 0) return TRUE;
    return FALSE;
}

§7. Logging schemas. The fact that I6 schemas are not much more than string makes them easy to log:

void Calculus::Schemas::log(i6_schema *sch) {
    Calculus::Schemas::write(DL, sch);
}

void Calculus::Schemas::write(OUTPUT_STREAM, i6_schema *sch) {
    if (sch == NULL) WRITE("<null schema>");
    else WRITE("<schema: %S>", &(sch->prototype));
}

void Calculus::Schemas::log_applied(i6_schema *sch, pcalc_term *pt1) {
    Calculus::Schemas::write_applied(DL, sch, pt1);
}

void Calculus::Schemas::write_applied(OUTPUT_STREAM, i6_schema *sch, pcalc_term *pt1) {
    if (sch == NULL) { WRITE("<null schema>"); return; }
    else {
        WRITE("<%S : ", &(sch->prototype));
        Terms::write(OUT, pt1);
        WRITE(">");
    }
}