4. Compiler
In Tutorial 4, we work with the parser, compiler and module system.
(Since this is for compiling Prolog code, this assumes some
familiarity
with the Prolog syntax.)
- Python: source/python/examples/YieldPrologTutorial/tutorial4.py
- C#: source/csharp/examples/YieldPrologTutorial/Tutorial4.cs
In Visual Studio .NET, you can load YieldPrologTutorial.sln . In
the project properties, make sure the Startup Object is Tutorial4.
- Javascript (requires yield
support such as Firefox 3): source/javascript/examples/YieldPrologTutorial/tutorial4.js
.
Open tutorial4.html
from
the same directory in a browser.
Dynamic Assert and Match
Let's modify the "brother" example
from Tutorial 2 by creating it dynamically.
Python
|
C#
|
YP.assertFact(Atom.a("brother"), \ [Atom.a("Hillary"), Atom.a("Hugh")]) YP.assertFact(Atom.a("brother"), \ [Atom.a("Hillary"), Atom.a("Tony")]) YP.assertFact(Atom.a("brother"), \ [Atom.a("Bill"), Atom.a("Roger")])
Brother = Variable() print("Using dynamic assert:") for l1 in YP.matchDynamic \ (Atom.a("brother"), \ [Atom.a("Hillary"), Brother]): print("Hillary has brother " + str(Brother.getValue()))
|
using System; using System.Collections.Generic; using YieldProlog; class Tutorial4 { static void Main(string[] args) { YP.assertFact(Atom.a("brother"), new object[] { Atom.a("Hillary"), Atom.a("Hugh") }); YP.assertFact(Atom.a("brother"), new object[] { Atom.a("Hillary"), Atom.a("Tony") }); YP.assertFact(Atom.a("brother"), new object[] { Atom.a("Bill"), Atom.a("Roger") });
Variable Brother = new Variable(); Console.WriteLine("Using dynamic assert:"); foreach (bool l1 in YP.matchDynamic (Atom.a("brother"), new object[] { Atom.a("Hillary"), Brother})) Console.WriteLine("Hillary has brother " + Brother.getValue() + "."); } }
|
Javascript |
YP.assertFact(Atom.a("brother"), [Atom.a("Hillary"), Atom.a("Hugh")]); YP.assertFact(Atom.a("brother"), [Atom.a("Hillary"), Atom.a("Tony")]); YP.assertFact(Atom.a("brother"), [Atom.a("Bill"), Atom.a("Roger")]);
var Brother = new Variable(); document.write("Using dynamic assert:<br>"); for (let l1 of YP.matchDynamic (Atom.a("brother"), [Atom.a("Hillary"), Brother])) document.write("Hillary has brother " + Brother.getValue() + ".<br>");
|
There are a few differences from the earlier example:
- Atom: Instead of
a
string like "Hillary",
we
use Atom.a("Hillary"),
where Atom.a
returns a
unique object of type Atom.
Each time you call Atom.a("Hillary")
it will return the same object, which means you can use the ==
operator
to instantly test for equality.
- assertFact: The
first time you call YP.assertFact(name, values) for a
certain name,
it creates a new list for the array of values. Each time you
call YP.assertFact
for the same
name, it appends the array of values to the list. [1]
(You can also use YP.prependFact.)
Note
that the name must be an Atom.
- matchDynamic:
Instead of calling the static function brother with the
arguments "Hillary"
and Brother, we
call YP.matchDynamic
with the name Atom.a("brother")
and the
arguments array containing Atom.a("Hillary")
and Brother. This
matches
the arguments with the values given to YP.assertFact. [2]
This prints the same result as before:
Using dynamic assert:
Hillary has brother Hugh.
Hillary has brother Tony.
Dynamic vs. static
In this tutorial, we want to learn to use the compiler which
produces
static functions, so why are we learning about dynamic matching?
Because to use the compiler properly, we need to control when the
compiler will try to call a function statically or dynamically. For
example, let us repeat the earlier static definition of parent:
Python
|
C#
|
def parent(arg1, arg2): for l1 in YP.unify \ (arg1, Atom.a("Chelsea")): for l2 in YP.unify \ (arg2, Atom.a("Hillary")): yield False for l1 in YP.unify \ (arg1, Atom.a("Chelsea")): for l2 in YP.unify \ (arg2, Atom.a("Bill")): yield False
|
using System; using System.Collections.Generic; using YieldProlog; class Tutorial4 { public static IEnumerable<bool> parent (object Person, object Parent) { foreach (bool l1 in YP.unify (Person, Atom.a("Chelsea"))) { foreach (bool l2 in YP.unify (Parent, Atom.a("Hillary"))) yield return false; } foreach (bool l1 in YP.unify (Person, Atom.a("Chelsea"))) { foreach (bool l2 in YP.unify (Parent, Atom.a("Bill"))) yield return false; } } }
|
Javascript |
function* parent(Person, Parent) { for (let l2 of YP.unify (Person, Atom.a("Chelsea"))) { for (let l3 of YP.unify (Parent, Atom.a("Hillary"))) { yield false; } } for (let l2 of YP.unify (Person, Atom.a("Chelsea"))) { for (let l3 of YP.unify (Parent, Atom.a("Bill"))) { yield false; } } }
|
When we compile uncle,
which calls parent and
brother, how will it
know which
is dynamic or static?
Compiling uncle
Here again is the Prolog code for uncle, which we want to parse and
compile:
uncle(Person, Uncle) :-
parent(Person, Parent),
brother(Parent, Uncle).
Python
|
C#
|
print "# Compiled code:" prologCode = \ "uncle(Person, Uncle) :- \n" + \ " parent(Person, Parent), \n" + \ " brother(Parent, Uncle). \n" YP.tell(sys.stdout) YP.see(YP.StringReader(prologCode)) TermList = Variable() PseudoCode = Variable() for l1 in parseInput(TermList): for l2 in makeFunctionPseudoCode \ (TermList, PseudoCode): convertFunctionPython(PseudoCode) YP.seen()
|
using System; using System.Collections.Generic; using YieldProlog; class Tutorial4 { static void Main(string[] args) { Console.WriteLine("// Compiled code:"); string prologCode = "uncle(Person, Uncle) :- \n" + " parent(Person, Parent), \n" + " brother(Parent, Uncle). \n"; YP.tell(Console.Out); YP.see(new StringReader(prologCode)); Variable TermList = new Variable(); Variable PseudoCode = new Variable(); foreach (bool l1 in Parser.parseInput(TermList)) { foreach (bool l2 in Compiler.makeFunctionPseudoCode (TermList, PseudoCode)) Compiler.convertFunctionCSharp(PseudoCode); } YP.seen(); } }
|
Javascript |
var prologCode = "uncle(Person, Uncle) :- \n" + " parent(Person, Parent), \n" + " brother(Parent, Uncle). \n"; YP.see(new YP.StringReader(prologCode)); var output = new YP.StringWriter(); YP.tell(output); var TermList = new Variable(); var PseudoCode = new Variable(); for (let l1 of parseInput(TermList)) { for (let l2 of makeFunctionPseudoCode (TermList, PseudoCode)) convertFunctionJavascript(PseudoCode); } YP.seen(); YP.told(); document.write("// Compiled code:<br>"); document.write (output.toString().replace (/\n/g,"<br>").replace(/ /g," "));
|
This parse and compile code follows these steps:
- Use YP.see to
set
the default input for the parser, in this case to read from the
prologCode string.
The input must end with a
newline.
- Use YP.tell to
set
the default output for the compiler.
- Call parseInput
to read from the default input and output the TermList.
- Call makeFunctionPseudoCode
to convert the TermList
to PseudoCode,
which is
an intermediate format which can be converted to several
languages.
This yields multiple times for the pseudo code for each function
we
want to output.
- Call convertFunctionPython,
convertFunctionJavascript
or convertFunctionCSharp
as appropriate for the target language. This writes to the
default
output.
Here is the compiled output for each target language:
Python
|
C#
|
# Compiled code: def getDeclaringClass(): return globals()
def uncle(Person, Uncle): Parent = Variable() for l1 in YP.matchDynamic \ (Atom.a("parent"), [Person, Parent]): for l2 in YP.matchDynamic \ (Atom.a("brother"), [Parent, Uncle]): yield False
|
// Compiled code: public class YPInnerClass {} public static Type getDeclaringClass() { return typeof(YPInnerClass).DeclaringType; }
public static IEnumerable<bool> uncle (object Person, object Uncle) { { Variable Parent = new Variable(); foreach (bool l2 in YP.matchDynamic (Atom.a("parent"), new object[] {Person, Parent})) { foreach (bool l3 in YP.matchDynamic (Atom.a("brother"), new object[] {Parent, Uncle})) { yield return false; } } } }
|
Javascript |
// Compiled code: function getDeclaringClass() { return null; }
function* uncle(Person, Uncle) { { var Parent = new Variable(); for (let l2 of YP.matchDynamic (Atom.a("parent"), [Person, Parent])) { for (let l3 of YP.matchDynamic (Atom.a("brother"), [Parent, Uncle])) { yield false; } } } }
|
(We will discuss getDeclaringClass
later.) Notice that in the uncle
function, the compiler used YP.matchDynamic
to call both parent
and brother. This is
what we want
for brother, but we want to call parent
statically. There are three ways in which the compiler will call a
function statically:
- Use import in
the
Prolog code
- Define the function locally in the same input file
- Use module Atom.a("")
when calling as a dynamic goal
We look at each of these below.
Using import to call a static
function
If we want to call a static function [3],
but
it is not defined in the
Prolog code, we can simply add an import
directive. (In Prolog,
if
you start a line with :-
it is a directive to the compiler. Don't forget to end with a
period.):
:- import('', [parent/2]).
uncle(Person, Uncle) :-
parent(Person, Parent),
brother(Parent, Uncle).
We use this as the prologCode
string in the same parse and compile code as above. Here is the
output:
Python
|
C#
|
# Calling an imported function: def getDeclaringClass(): return globals()
def uncle(Person, Uncle): Parent = Variable() for l1 in parent(Person, Parent): for l2 in YP.matchDynamic \ (Atom.a("brother"), [Parent, Uncle]): yield False
|
// Calling an imported function: public class YPInnerClass {} public static Type getDeclaringClass() { return typeof(YPInnerClass).DeclaringType; }
public static IEnumerable<bool> uncle (object Person, object Uncle) { { Variable Parent = new Variable(); foreach (bool l2 in parent(Person, Parent)) { foreach (bool l3 in YP.matchDynamic (Atom.a("brother"), new object[] {Parent, Uncle})) { yield return false; } } } }
|
Javascript |
// Calling an imported function: function getDeclaringClass() { return null; }
function* uncle(Person, Uncle) { { var Parent = new Variable(); for (let l2 of parent(Person, Parent)) { for (let l3 of YP.matchDynamic (Atom.a("brother"), [Parent, Uncle])) { yield false; } } } }
|
Notice that parent is
called statically.
The import directive
has
two arguments. The first argument is the module where the imported
function is found, which is always ''. [4]
For C#, this means the imported function is in the same class as the
calling function. For Javascript and Python, this means the imported
function is in the global scope.
The second argument to import
is the comma-separated list of imported functions, where each member
of
the list is name/n, where name
is the name of the function and n is
the number of
arguments. In this example, parent
has two arguments, so we use parent/2.
Calling local
functions statically
Instead of using import,
a way to get the same result is to define parent in the same input with uncle:
parent('Chelsea', 'Hillary').
parent('Chelsea', 'Bill').
uncle(Person, Uncle) :-
parent(Person, Parent),
brother(Parent, Uncle).
We use this as the prologCode
string in the same parse and compile code as above. Here is the
output:
Python
|
C#
|
# Calling a locally-defined function: def getDeclaringClass(): return globals()
def parent(arg1, arg2): for l1 in YP.unify(arg1, Atom.a("Chelsea")): for l2 in YP.unify(arg2, Atom.a("Hillary")): yield False for l1 in YP.unify(arg1, Atom.a("Chelsea")): for l2 in YP.unify(arg2, Atom.a("Bill")): yield False
def uncle(Person, Uncle): Parent = Variable() for l1 in parent(Person, Parent): for l2 in YP.matchDynamic \ (Atom.a("brother"), [Parent, Uncle]): yield False
|
// Calling a locally-defined function: public class YPInnerClass {} public static Type getDeclaringClass() { return typeof(YPInnerClass).DeclaringType; }
public static IEnumerable<bool> parent (object arg1, object arg2) { { foreach (bool l2 in YP.unify (arg1, Atom.a("Chelsea"))) { foreach (bool l3 in YP.unify (arg2, Atom.a("Hillary"))) { yield return false; } } } { foreach (bool l2 in YP.unify (arg1, Atom.a("Chelsea"))) { foreach (bool l3 in YP.unify (arg2, Atom.a("Bill"))) { yield return false; } } } }
public static IEnumerable<bool> uncle(object Person, object Uncle) { { Variable Parent = new Variable(); foreach (bool l2 in parent(Person, Parent)) { foreach (bool l3 in YP.matchDynamic (Atom.a("brother"), new object[] {Parent, Uncle})) { yield return false; } } } }
|
Javascript |
// Calling a locally-defined function: function getDeclaringClass() { return null; }
function* parent(arg1, arg2) { { for (let l2 of YP.unify (arg1, Atom.a("Chelsea"))) { for (let l3 of YP.unify (arg2, Atom.a("Hillary"))) { yield false; } } } { for (let l2 of YP.unify (arg1, Atom.a("Chelsea"))) { for (let l3 of YP.unify (arg2, Atom.a("Bill"))) { yield false; } } } }
function* uncle(Person, Uncle) { { var Parent = new Variable(); for (let l2 of parent(Person, Parent)) { for (let l3 of YP.matchDynamic (Atom.a("brother"), [Parent, Uncle])) { yield false; } } } }
|
Notice that the compiled version of parent is basically the same as
the hand-coded one above (with
different argument names). Also note that in uncle, parent is called statically
because it is defined in the same input code.
In summary, it is preferred to put all the static functions in the
same
input file since all functions need to be in the same class (for C#)
or
in the global scope (for Python and Javascript). However, if you
want
to hand-code a static function, or don't have all functions
available
when you call parseInput,
then use the import directive
to
statically call them. This covers most cases, but we still need to
look at dynamic goals.
Module Atom.a("") for dynamic
goals
Prolog allows you to define and call code dynamically. For example,
let
us modify uncle to put
parent inside a dynamic
goal
and then call the goal:
:- import('', [parent/2]).
uncle(Person, Uncle) :-
Goal = parent(Person, Parent),
Goal,
brother(Parent, Uncle).
We use this as the prologCode
string in the same parse and compile code as above. Here is the
output:
Python
|
C#
|
# Calling a dynamic goal: def getDeclaringClass(): return globals()
def uncle(Person, Uncle): Goal = Variable() Parent = Variable() for l1 in YP.unify \ (Goal, Functor2 \ (Atom.a("parent", Atom.a("")), \ Person, Parent)): for l2 in YP.getIterator \ (Goal, getDeclaringClass()): for l3 in YP.matchDynamic \ (Atom.a("brother"), [Parent, Uncle]): yield False
|
// Calling a dynamic goal: public class YPInnerClass {} public static Type getDeclaringClass() { return typeof(YPInnerClass).DeclaringType; }
public static IEnumerable<bool> uncle (object Person, object Uncle) { { Variable Goal = new Variable(); Variable Parent = new Variable(); foreach (bool l2 in YP.unify (Goal, new Functor2 (Atom.a("parent", Atom.a("")), Person, Parent))) { foreach (bool l3 in YP.getIterator (Goal, getDeclaringClass())) { foreach (bool l4 in YP.matchDynamic (Atom.a("brother"), new object[] {Parent, Uncle})) { yield return false; } } } } }
|
Javascript |
// Calling a dynamic goal: function getDeclaringClass() { return null; }
function* uncle(Person, Uncle) { { var Goal = new Variable(); var Parent = new Variable(); for (let l2 of YP.unify (Goal, new Functor2 (Atom.a("parent", Atom.a("")), Person, Parent))) { for (let l3 of YP.getIterator (Goal, getDeclaringClass())) { for (let l4 of YP.matchDynamic (Atom.a("brother"), [Parent, Uncle])) { yield false; } } } } }
|
In uncle, we first
unify Goal with a Functor2 which represents
the
term parent(Person, Parent).
Notice that Atom.a("parent", Atom.a(""))
has a second argument which is the module that declared the Atom. (Atom.a("") is the same as
the
Prolog code for the module ''
in the import
directive.)
As before, the declaring module is Atom.a("") if the function is
in
the import directive,
or
if a function with the atom's name is defined locally in the same
input.
Next, we pass the Goal
to
YP.getIterator which
examines the goal and figures out how to call it. YP.getIterator sees that it
needs to call parent
and
that the Atom for parent was declared in the
module Atom.a("")
which
means "the same module as the caller". This is why the caller always
passes getDeclaringClass().
For Python and Javascript, getDeclaringClass
returns null which means the global scope. For C#, getDeclaringClass uses a
trick
with the locally-defined YPInnerClass
to get the Type object
of
the class of the caller. In this way, YP.getIterator finds and calls
the statically defined parent
function. (If the Atom
for the function name is defined without a module or with the module
Atom.NIL, then YP.getIterator uses YP.matchDynamic.)
In summary, you will rarely have to write code which directly calls
YP.getIterator or getDeclaringClass. But it
is
important to understand how static functions are called from dynamic
goals, especially if a dynamic goal is passed as an argument from
one
function to another before it is called.
1. The length of the values array is called
the
"arity". Strictly speaking, to add to the same list, the name and
"arity" must be the same. If the length of the values array is
different, it is considered a different list in the same way that
functions in C# are different if they have different numbers of
arguments.
2. YP.assertFact
actually stores the values in an IndexedAnswers
object which is indexed dynamically to be very efficient.
3. Prolog uses the term "predicate", but we
say
"function" because we are focussed on the function which the
compiler
produces for the target language.
4. In the future, Yield Prolog may support
general module names but this is more complicated because we would
need
a system where you register a class with a module name. For now, we
just allow '' which
means
"the same class as the caller" so the caller can just use the
locally
defined getDeclaringClass
to find its own "module".
If you really need to statically call a function in a different
class
(until Yield Prolog supports general module names), there is an
inelegant way to do it. Here is how to call parent if it is defined in
OtherClass:
:- import('', ['OtherClass.parent'/2]).
uncle(Person, Uncle) :-
'OtherClass.parent'(Person, Parent),
brother(Parent, Uncle).
In other words, you substitute parent
everywhere with 'OtherClass.parent'.