Terms are the representations of values in predicate calculus: variables, constants or functions of other terms.
- §1. About terms
- §4. Creating new terms
- §5. Copying
- §6. Variable letters
- §8. Underlying terms
- §9. Adjective-noun conversions
- §11. Writing to text
§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.
- (a) Variables are represented by the numbers 0 to 25, and -1 means "not a variable".
- (b) Constants are pointers to specification structures of main type VALUE, and NULL means "not a constant".
- (c) Functions are pointers to pcalc_func structures (see below), and NULL means "not a function".
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;
- The structure pcalc_term is accessed in 4/ap, 4/prp, 4/bas, 4/tcp, 5/sc, 5/smp and here.
§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:
- (1) A pcalc_term structure which has a function field pointing to
- (2) A pcalc_func structure whose bp field points to A, and whose fn_of field is
- (3) A pcalc_term structure which has a function field pointing to
- (4) A pcalc_func structure whose bp field points to B, and whose fn_of field is
- (5) A pcalc_term structure which has a function field pointing to
- (6) A pcalc_func structure whose bp field points to C, and whose fn_of field is
- (7) A pcalc_term structure which has a variable field set to 0 (which is \(x\)).
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;
- The structure pcalc_func is accessed in 4/bas, 4/tcp, 5/smp and here.
§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
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;
- This code is used in §4 (three times).
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.
1 Well, perhaps not a string theorist. "There aren't enough small numbers to meet the many demands made of them" (Richard Guy). ↩
2 Strictly speaking there is also _, but we won't go there. ↩
§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
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>"); } }