This is an example for building Prolog term matching with some design patterns in C++. The patterns include Builder, Composite, Factory Method, Flyweight, Interpreter, Iterator, Proxy, Singleton, and Template Method. We only implement three operators, matching( = ), conjunction( , ), and disjunction( ; ), in this simple interpreter and try to make our output like swi-prolog. Here are some output examples:
Constant matching
-? tom = tom.
true.
-? tom = jerry.
false.
Variable matching
-? X = 1.
X = 1.
Structure(Compound) matching
-? point(X, 1) = point(0, 1).
X = 0.
List matching(syntax sugar of structure)
-? [tom, jerry] = [H|T].
H = tom,
T = [jerry].
Shared variables
-? A = s(B), A = C, B = D, A = D.
A = B,
B = C,
C = D,
D = s(D).
Conjunction and Disjunction
-? X = 1, X = 2.
false.
-? X = 1; X = 2.
X = 1;
X = 2.
Prerequest for Windows 10
Install GoogleTest for running unit test
Open your terminal or command line and do
make test
under your project folder and then you should see the following result
A query to the Prolog interpreter consists of one or more goals. For example, in
-? X = 1.
X = 1.
there is a relational goal "X = 1" which means "is X matchable to 1?". A goal is something that Prolog tries to satisfy. In here, "X = 1" succeeded because the variable X matches any legitimate term before instantiating.
-? X = 1, X = 2.
false.
there is another example, which contains two relational goals "X = 1" and "X = 2". It means "is X matchable to 1 and 2?". Unfortunately, the Prolog says no by printing "false" because the variable X can't be 1 and 2 at the same time.
-? X = 1; X = 2.
X = 1;
X = 2.
the final example also contains two relational goals. But it means "is X matchable to 1 or 2?". So the goal can be satisfied by instantiating 1 or 2 to X.
In this example, we reference iso prolog to define the following data objects of prolog:
A series of letters, digits, and/or underscores, beginning with a lowercase letter
ex: atom, a_t_o_m, atom123
A series of 1 or more characters from the set #$&*+-./:<->?@^~\
provided it does not begin with /*
(such atoms are called graphic tokens)
ex: :->, @-@?, ^+$*
A series of arbitrary characters in single quotes
ex: 'This is an atom, LoL'
The special atoms [] and {}
ex: [], {}
A series of demical digits.
ex: 1, 0.2, -0.3
A variable name begin with a captical letter or the underscore mark(_) and consists of letters, digits, and/or underscores.
ex: Var, _var, Var123, V_A_R
The ordinary way to write a structure is to write the functor, an opening parenthesis, a series of terms separeted by commas, and a closing parenthesis. p.s. the functor should be an atom.
ex: date(feb, 19, 2018), age(tom, X)
List is defined as rightward-nested structures using the operator '.'.
ex: [a] = .(a, [])
[a, b] = .(a, .(b, []))
[a | b] = .(a, b)
Let S and T be two terms.
- If S and T are constants then they match only if both are the same object.
-? tom = tom.
true. (match success)
-? tom = jerry.
false. (match fail)
- If one of them is variable then they match only if
- the variable has not been instantiated, or
- the instance of variable is equal to the other one
-? X = 1.
X = 1. (match success, X has been instantiated to 1)
-? X = 1, X = 2.
false. (match fail, the instance of X is 1 not 2)
- If S and T are structures then they match only if
- both have the same functor,
- equal arity of arguments, and
- all corresponding arguments match.
-? year(2018) = yEar(2018).
false. (match fail, different functor)
-? date(feb, 19, 2018) = date(feb, 19).
false. (match fail, different arity)
-? date(jan, 19, 2018) = date(feb, 19, 2018).
false. (match fail, 'jan' is not equal to 'feb')
-? date(feb, 19, 2018) = date(feb, 19, 2018).
true. (match success)
lexeme | token-name | attribute-value |
---|---|---|
Atom definition | Atom | Symbol of atom |
Number definition | Number | Value of number |
Variable definition | Var | Symbol of variable |
<query> ::= <disjunction expression> < disjunction expression'> '.'
<disjunction expression> ::= <conjunction expression> <disjunction expression'>
<disjunction expression'> ::= ';' <conjunction expression> <disjunction expression'> *
<conjunction expression> ::= <matching expression> <conjunction expression'>
<conjunction expression'> ::= ',' <matching expression> <conjunction expression'> *
<matching expression> ::= <term> '=' <term>
<structure> ::= <atom> '(' <terms> ')'
<terms> ::= <term> *
<term> ::= <atom> | <number> | <variable> | <structure>
<atom> ::= <lowercase letter> <string> | <specialchar>+ | '<string>'
<number> ::= <negative>?<digit>+
<variable> ::= <uppercase letter> <string>
<string> ::= <character> *
<character> ::= <digit> | <lowercase letter> | <uppercase letter>
<negative> ::= -
<digit> ::= [0 - 9]
<lowercase letter> ::= [a - z]
<uppercase letter> ::= [A - Z] | _
<specialchar> ::= # | $ | & | * | + | - | . | / | : | < | - | > | ? | @ | ^ | ~ | \
Bottom-up parsing starts from the leaf nodes of a tree and works in upward direction till it reaches the root node. Here, we use a simple query, A = 1, B = 2, C = 3; D = 4.
, to explain how the parser works in the following figures.
parse tree | build steps |
---|---|
In order to build a right recursive tree like the left one shows. We need some stacks to remember which terms, operators, or expressions has been parsed and pop them up to create other expressions if the priority of new operator is greater than the top of the operator stack. Then the parse steps will just be like the right figure.
There are two situations may cause cycle when variable matching.
- Instantiate to itself
-? X = X.
- Instantiate to a structure(compound) which arugments contain itself
-? X = s(X).
In the first situation, it only occurs when a variable match itself or the last shared variable instantiates to the first one. So we check if there is a shared variable and it is equal to the reference one to avoid cycle.
Variable* other // which prepares to be an instance
for (; other != nullptr; other = other->instance())
if (other == this)
return true; // has cycle
return false;
The other one, we use a flag to check if this variable has been visited or not.
// hasBeenVisited is a bool property of variable
if (hasBeenVisited) {
// do something when cycle detection
}
hasBeenVisited = true;
// do something on instance
hasBeenVisited = false;
In the swi-prolog, all variables are sorted by its address. Here, we use a int property called age to complete the same effect and sort the shared variables when instantiating a new variable to another variable by matching.
Click the following image if you want to see the full version