The set of procedures given above enables us to recognize when we have a sentence which satisfies our grammar. But we need more than this. We want procedure S to produce a database query instead of <true>. We have seen how to do this by constructing the meaning of a sentence from the meanings of its syntactic constituents. The meanings of these constituents are in turn composed from the meanings of their syntactic constituents and so on.
This can be arranged within procedure S by extracting a meaning from the NP restriction procedure when there is a successful pattern match and composing it into a meaning for the entire sentence. To achieve this, NP must also be modified to return a meaning instead of <true>. If the noun-phrase is a proper name, the returned meaning is just a list containing that name. Otherwise, a list of database patterns is composed from the head noun in the noun-phrase and the meaning of an embedded noun-phrase if this is present.
The way in which this meaning is composed depends on which of the three phrase-structure rules with NP on the left-hand side describes the noun-phrase. We shall give new versions of S and NP which do just this. Although only one new term will be introduced, the procedures are considerably more difficult to follow than the ones we have given so far. We only show them here for the sake of completeness, and to demonstrate that a parser and meaning generator can be written in very few lines of POP-11.
To define the procedures there has to be some way of obtaining the value returned by a restriction procedure. Fortunately there is a feature of the pattern matcher that makes this possible. In normal use a pattern variable is set equal to the word or list of words with which it matches. When a restriction procedure returns <true>, this remains the case. However, when a restriction procedure returns anything other than <true> or <false>, the match succeeds, and furthermore the pattern variable is set equal to that value instead of the word or series of words appearing in the matched list.
define S(list) -> meaning; vars np, sym; if list matches [how do i get to ??np:NP] or list matches [can you tell me how to get to ??np:NP] then if np matches [[= ?sym isa =] ==] then ;;; meaning of noun-phrase is ;;; a list of patterns [ [? ^sym underground ? destination] ^^np ] -> meaning else ;;; meaning of noun-phrase is a proper name [ [^np underground ? destination] ] -> meaning endif else ;;; unknown sentence form false -> meaning endif; enddefine; define NP(list) -> meaning; vars pn, d, n, p, np, sym1, sym2; if list matches [??pn:PROPN] then pn -> meaning elseif list matches [?d:DET ??n:NOUN] then gensym("v") -> sym1; [ [ ? ^sym1 isa ^n] ] -> meaning elseif list matches [?d:DET ??n:NOUN ?p:PREP ??np:NP] then gensym("v") -> sym1; if np matches [[= ?sym2 isa =] ==] then ;;; meaning of noun-phrase is ;;; a list of patterns [ [? ^sym1 isa ^n] [? ^sym1 ^p ? ^sym2] ^^np] -> meaning else ;;; meaning of noun-phrase is proper name [ [? ^sym1 isa ^n] [? ^sym1 ^p ^np] ] -> meaning endif; else ;;; unknown noun-phrase form false -> meaning endif; enddefine;
In both S and NP, when the returned meaning from an embedded NP is a list of patterns, the name of the first variable is extracted by matching it against ?sym, and this is used to form a new pattern which is tagged onto the front of the original list. This is necessary to ensure that certain variables in lists of patterns refer to the same objects.
In constructing new patterns a `?' must be inserted before pattern variable names which are themselves the values of ordinary procedure variables. As you can see this has been achieved using the up-arrow, in a list of the form:
[ ...? ^
sym ...]
to give
[ ...? v5 ...]
where v5 is the value of sym. The list is now of a form that can be matched against the database (for example, [?v5 isa [park]]).
One small problem is the use of variable names within patterns. By virtue of our grammar, there may be an indefinite number of embedded noun-phrases within a place description. Consequently, an indefinite number of unique variable names will be required. A standard way of producing unique variable names is to append an increasing sequence of numbers onto the end of a word (e.g., v1, v2, v3 ...). The built-in POP-11 procedure gensym produces unique variable names which can be used in patterns. It is called with a word as argument and returns this word with a number appended onto the end. On subsequent uses of gensym the appended number is increased. Thus,
gensym("v") =>
** v1
gensym("v") =>
** v2
The procedures PROPN, NOUN, DET, and PREP could also be modified to return a meaning. It is not not necessary to do so for this example, since in each case the required meaning is identical to the input word or words. Hence, any pattern variable restricted by PROPN, NOUN, DET, or PREP will have as its value the desired meaning following a successful match. The versions of PROPN, NOUN, DET, and PREP in appendix B at the back of the book are slightly different in that they allow a greater range of words, including synonyms, and return one out of a list of synonyms, in order to facilitate matching against the database.
Here are some calls of the procedures:
NP([the gallery in the square]) =>
** [[? v2 isa [gallery]] [? v2 in ? v1] [? v1 isa [square]]]
NP([the park containing the lake])=>
** [[? v4 isa [park]] [? v4 containing ? v3] [? v3 isa [lake]]]
The result of each call is either <false> or a pattern which can be matched against the database using which. The main procedure S is now in the correct form to be called from within answer, with the following result:
answer([how do i get to the gallery in the square containing the monument])=>>
travelling by underground,
take the victoria line to green park
then change and take the jubilee line to charing cross