[Terms::] Terms. Terms are the representations of values in predicate calculus: variables, constants or functions of other terms. @h 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 //imperative: Cinders and Deferrals//, 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 |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; @ Terms are really quite simple, as the following //calculus-test// exercise shows: = (text from Figures/terms.txt as REPL) @h Creating new terms. = pcalc_term Terms::new_variable(int v) { pcalc_term pt; @; 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; @; 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; @; pcalc_func *pf = CREATE(pcalc_func); pf->bp = bp; pf->fn_of = ptof; pf->from_term = t; pt.function = pf; return pt; } @ Where, in all three cases: @ = pt.variable = -1; pt.constant = NULL; pt.function = NULL; pt.cinder = -1; /* that is, no cinder */ pt.term_checked_as_kind = NULL; @h 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; } @h 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 anyone[1] 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. @ 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: = char *pcalc_vars = "xyzabcdefghijklmnopqrstuvw"; @h 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; } @h 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 @ 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; } @h 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(""); } 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("", j); } else { WRITE(""); } }