mirror of
https://github.com/ganelson/inform.git
synced 2024-07-05 16:44:21 +03:00
221 lines
8.1 KiB
OpenEdge ABL
221 lines
8.1 KiB
OpenEdge ABL
[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; @<Make new blank term structure 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; @<Make new blank term structure 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; @<Make new blank term structure 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:
|
|
|
|
@<Make new blank term structure pt@> =
|
|
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("<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>");
|
|
}
|
|
}
|