Terms are the representations of values in predicate calculus: variables, constants or functions of other terms.


§1. About terms. A "term" can be a constant, a variable, or a function of another term: see What This Module Does. Our data structure therefore falls into three cases. At all times exactly one of the three relevant fields, variable, constant and function is used.

Cinders are discussed in Cinders and Deferrals (in imperative), and can be ignored for now.

In order to verify that a proposition makes sense and does not mix up incompatible kinds of value, we will need to type-check it, and one part of that involves assigning a kind of value \(K\) to every term \(t\) occurring in the proposition. This calculation does involve some work, so we cache the result in the term_checked_as_kind field.

typedef struct pcalc_term {
    int variable;  0 to 25, or -1 for "not a variable"
    struct parse_node *constant;  or NULL for "not a constant"
    struct pcalc_func *function;  or NULL for "not a function of another term"
    int cinder;  complicated, this: used to worry about scope of I6 local variables
    struct kind *term_checked_as_kind;  or NULL if unchecked
} pcalc_term;

§2. The pcalc_func structure represents a usage of a function inside a term. Terms such as \(f_A(f_B(f_C(x)))\) often occur, an example which would be stored as:

typedef struct pcalc_func {
    struct binary_predicate *bp;  the predicate B
    int from_term;  which term of the predicate this derives from
    struct pcalc_term fn_of;  the term to which we apply the function
} pcalc_func;

§3. Terms are really quite simple, as the following calculus-test exercise shows:

'new binary sees (none, sees-f1)': ok
'new binary knows (knows-f0, none)': ok
'term 7': '7'
'term z': z
'term sees-f1 (7)': <sees-f1(*1) : '7'>
'term knows-f0 (sees-f1 (x))': <knows-f0(*1) : <sees-f1(*1) : x>>
'constant underlying 7': '7'
'constant underlying y': --
'constant underlying sees-f1 (7)': '7'
'variable underlying 7': --
'variable underlying y': y
'variable underlying knows-f0 (sees-f1 (x))': x

§4. Creating new terms.

pcalc_term Terms::new_variable(int v) {
    pcalc_term pt; Make new blank term structure pt4.1;
    if ((v < 0) || (v >= 26)) internal_error("bad variable term created");
    pt.variable = v;
    return pt;
}

pcalc_term Terms::new_constant(parse_node *c) {
    pcalc_term pt; Make new blank term structure pt4.1;
    pt.constant = c;
    return pt;
}

pcalc_term Terms::new_function(struct binary_predicate *bp, pcalc_term ptof, int t) {
    if ((t < 0) || (t >= MAX_ATOM_ARITY)) internal_error("term out of range");
    pcalc_term pt; Make new blank term structure pt4.1;
    pcalc_func *pf = CREATE(pcalc_func);
    pf->bp = bp; pf->fn_of = ptof; pf->from_term = t;
    pt.function = pf;
    return pt;
}

§4.1. Where, in all three cases:

Make new blank term structure pt4.1 =

    pt.variable = -1;
    pt.constant = NULL;
    pt.function = NULL;
    pt.cinder = -1;  that is, no cinder
    pt.term_checked_as_kind = NULL;

§5. Copying.

pcalc_term Terms::copy(pcalc_term pt) {
    if (pt.constant) pt.constant = Node::duplicate(pt.constant);
    if (pt.function) pt = Terms::new_function(pt.function->bp,
        Terms::copy(pt.function->fn_of), pt.function->from_term);
    return pt;
}

§6. Variable letters. The number 26 turns up quite often in this chapter, and while it's normally good style to define named constants, here we're not going to. 26 is a number which anyone1 will immediately associate with the size of the alphabet. Moreover, we can't really raise the total, because we will want to compile these with single-character identifier names, a to z.2 To have a variable limit lower than 26 would be artificial, since there are no memory constraints arguing for it; but a proposition with 27 or more variables would be too huge to evaluate at run-time in any remotely plausible length of time. So although the 26-variables-only limit is embedded in Inform, it really is not any restriction, and it greatly simplifies the code.

§7. The variables 0 to 25 are referred to by the letters \(x, y, z, a, b, c, ..., w\), as provided for by this lookup array:

inchar32_t *pcalc_vars = U"xyzabcdefghijklmnopqrstuvw";

§8. Underlying terms. Routines to see if a term is a constant \(C\), or if it is a chain of functions at the bottom of which is a constant \(C\); and similarly for variables.

parse_node *Terms::constant_underlying(pcalc_term *t) {
    if (t == NULL) internal_error("null term");
    if (t->constant) return t->constant;
    if (t->function) return Terms::constant_underlying(&(t->function->fn_of));
    return NULL;
}

int Terms::variable_underlying(pcalc_term *t) {
    if (t == NULL) internal_error("null term");
    if (t->variable >= 0) return t->variable;
    if (t->function) return Terms::variable_underlying(&(t->function->fn_of));
    return -1;
}

§9. Adjective-noun conversions. As we shall see, a general unary predicate stores a type-reference pointer to an adjectival phrase — the adjective it tests. But sometimes the same word acts both as adjective and noun in English. In "the green door", clearly "green" is an adjective; in "the door is green", it is possibly a noun; in "the colour of the door is green", it must surely be a noun. Yet these are all really the same meaning. To cope with this ambiguity, we need a way to convert the adjectival form of such an adjective into its noun form, and back again.

#ifdef CORE_MODULE
pcalc_term Terms::adj_to_noun_conversion(unary_predicate *tr) {
    adjective *aph = AdjectivalPredicates::to_adjective(tr);
    instance *I = AdjectiveAmbiguity::has_enumerative_meaning(aph);
    if (I) return Terms::new_constant(Rvalues::from_instance(I));
    property *prn = AdjectiveAmbiguity::has_either_or_property_meaning(aph, NULL);
    if (prn) return Terms::new_constant(Rvalues::from_property(prn));
    return Terms::new_variable(0);
}
#endif

§10. And conversely:

unary_predicate *Terms::noun_to_adj_conversion(pcalc_term pt) {
    #ifdef CORE_MODULE
    parse_node *C = pt.constant;
    if (Node::is(C, CONSTANT_NT) == FALSE) return NULL;
    kind *K = Node::get_kind_of_value(C);
    if (Properties::property_with_same_name_as(K) == NULL) return NULL;
    if (Kinds::Behaviour::is_an_enumeration(K)) {
        instance *I = Node::get_constant_instance(C);
        return AdjectivalPredicates::new_up(Instances::as_adjective(I), TRUE);
    }
    #endif
    return NULL;
}

§11. Writing to text. The art of this is to be unobtrusive; when a proposition is being logged, we don't much care about the constant terms, and want to display them concisely and without fuss.

void Terms::log(pcalc_term *pt) {
    Terms::write(DL, pt);
}
void Terms::write(text_stream *OUT, pcalc_term *pt) {
    if (pt == NULL) {
        WRITE("<null-term>");
    } else if (pt->constant) {
        parse_node *C = pt->constant;
        if (pt->cinder >= 0) { WRITE("const_%d", pt->cinder); return; }
        if (Wordings::nonempty(Node::get_text(C))) { WRITE("'%W'", Node::get_text(C)); return; }
        #ifdef CORE_MODULE
        if (Node::is(C, CONSTANT_NT)) {
            instance *I = Rvalues::to_object_instance(C);
            if (I) { Instances::write(OUT, I); return; }
        }
        #endif
        Node::log_node(OUT, C);
    } else if (pt->function) {
        binary_predicate *bp = pt->function->bp;
        i6_schema *fn = BinaryPredicates::get_term_as_fn_of_other(bp, 1-pt->function->from_term);
        if (fn == NULL) internal_error("function of non-functional predicate");
        Calculus::Schemas::write_applied(OUT, fn, &(pt->function->fn_of));
    } else if (pt->variable >= 0) {
        int j = pt->variable;
        if (j<26) WRITE("%c", pcalc_vars[j]); else WRITE("<bad-var=%d>", j);
    } else {
        WRITE("<bad-term>");
    }
}