I wrote yesterday about getting CoPilot to write an interpreter for a simple subset of Prolog in Pascal. There were only one or two syntax errors, so I was quickly able to fix them and get the interpreter running. Once I did, though, it quickly became clear that there were problems with the code, probably in the parsing of facts and rules, so I added a 'show' command that would display the facts and rules as they had been parsed.
This produced a great deal of rubbish. After tracking this line by line, I found the reason. For example, when the interpreter parses the fact 'parent (noam, netta)', a record of type TPredicate is created; such a record has a name field (this case, parent) and an args field, which is a dynamic array of terms, where each term consists of a kind (either an atom or a variable) and a value. For the current fact, the data will be
name: parent
args[0].type : tkAtom
args[0].value : noam
args[1].type : tkAtom
args[1].value : netta
The fact has been parsed correctly, but when that fact is added to the global list of facts, something goes wrong. Eventually I tracked this down to the statement
Move(Args[0], Result.Args[0], Length(Args) * SizeOf(TTerm));
Too much data was being added which is why the 'show' command produced a great deal of rubbish. I replaced that line with the following
for i:= 0 to high (args) do
result.args[i]:= args[i];
Moving onto rules, I saw that the same problem existed regarding the use of 'move'. This was easily corrected, but I could see that the parsing was going haywire. If, for example, one has the rule
grandparent (X, Z):- parent (X, Y), parent (Y, Z).
The 'head', i.e. grandparent (X, Z) is parsed correctly, but the brackets cause the parsing of the 'tail' as per CoPilot to return wrong values. CoPilot uses a tstringlist to store the various tokens, where the comma is used as a delimiter. As a result, the following tokens are returned
parent (X
Y
parent (Y
Z
if not worse. This is clearly wrong: the parser should initially create two terms, parent (X, Y) and parent (Y, Z), then use exisiting code to parse each term correctly. After asking, CoPilot produced code that handles multiple terms in a rule body correctly. Showing data that has been loaded produces the correct output. It would have been better had CoPilot create a recursive descent parser from the beginning instead of something more ad hoc.
Unfortunately, running a query such as grandparent (noam, Y) does not return the correct results, so now I'll have debug the query part of the code. That's the most interesting part.
To start with, the code does not backtrack, i.e. find multiple solutions. Well, it does but only the last solution is displayed. This is because the local environment that contains the values for the various variables overwrites the global environment, so even if several solutions are found, only the final solution was saved in the global environment and then displayed. Fixed.
And in fixing that part of finding multiple solutions for matching facts, I've inadvertently caused problems for finding solutions to rules. For a rule, first a possible solution should be found for the first term (in the case above, parent (X, Y)), then use those values for the second term and possibly more. Saving all the values for the first term and then trying to solve the second term ruins everything.
This day in blog history: