The Prolog.NET language is an implementation of Prolog on the .NET Framework. The motivation behind this project is to porvide a logic programming language for the .NET framework. By combining the logic/declarative nature of Prolog with .NET’s language interoperability, we make use of Prolog an efficient declarative meta-language to reason about the structure and behavior of .NET programs and assemblies.
Language Design/Compiler Architecture
The Prolog.NET system consists of a Prolog compiler and a runtime environment. The compiler (prologc.exe) is responsible for compiling Prolog programs into either abstract machine code to be executed by the runtime environment or .NET intermediate language executed directly by the .NET Common Language Runtime. The runtime environment is a .NET implementation of a Warren Abstract Machine with an extended instruction set that facilitates interoperability between Prolog and .NET. Upon installing Prolog.NET, the Compiler libraries as well as the runtime environment will be automatically installed in the Global Assembly Cache (GAC) by the installer.
There are three output formats that a Prolog program can be compiled into using the Prolog.NET compiler: Executables (.exe), Class libraries (.dll), or Abstract Machine files (.xml). Both executables and class libraries will require the runtime environment installed in the global assembly cache in order to execute. However, the compiler also uses the ILMerge utility from Microsoft Research to merge the runtime environment library with the executable or DLL so that they can be run or linked on a system that doesn’t have the runtime environment installed. Abstract machine files are XML files that represent the Prolog programs in Warren Abstract Machine format. These files can be executed using the prolog.exe program, which acts as the main driver for the runtime environment interpreter.
Compiling Prolog programs into .NET assemblies
Prolog source code can be compiled into .NET assemblies using the Prolog.NET compiler. There are two types of assemblies that can be generated using the compiler: dynamically-linked libraries and executables. The .NET assemblies generated will require the runtime library installed in the global assembly cache (GAC). However, the compiler can also merge the generated assembly with the Prolog runtime library to implement a completely independent static executable. The type of assembly target can be specified via the /target switch in the prologc executable.
Generating a .NET Library Assembly (DLL)
In order to compile a Prolog program into a DLL assembly the /target:dll switch must be specified with the compiler. Every Prolog source code file is compiled into one .NET class. The class name has to be specified via the class/1 directive in the source file. Once compiled, every Prolog predicate is compiled into a public class member method. Since predicates can either be true or false, we set the return types of the compiled methods to be System.Boolean. The examples below show how a compiled Prolog program would look like if reverse-engineered in C#:
The class in table 1 above would be compiled via the following command
Compiled .NET assembly
Namepsaces can be specified via the namespace/1 directive in a Prolog program. The parameter type has type System.Object which allows us to pass arbitrary variable types to the predicate. this is known in .NET as “boxing”. Prolog.NET Runtime provides the AbstractTerm class type which is the equivalent of a Prolog variable. AbstractTerm acts as a container for a value obtained after unifying it with a predicate term. In order to use AbstractTerm, the Axiom.Runtime namespace must be used. The following is a list of Prolog and .NET equivalent data types that can be used to call a Prolog predicate:
Language Data Types
|Prolog Data type||.NET Data Type|
An example of data conversion is shown in the example below after compiling the above Prolog code and using factorial/2 and fib/2 predicates in .NET:
Generating a .NET Process Assembly (EXE)
The Prolog.NET compiler can compile Prolog programs into .NET executable assemblies via the /target:exe switch. By default, the executable generated will require having Prolog.NET installed in order to execute; this is because it makes use of the Runtime library (which is installed in the GAC). However, you can provide the /static switch to the compiler to build a static executable that can be executed on a system without the Prolog.NET runtime installed. In Prolog.NET, the main/0 predicate is the entry point of the executable at runtime. For instance, the famous hello world program:
Would be compiled through this command:
The hello.pro program above can also be compiled into a static executable through the following command:
A static executalbe is one that doesn't require the Prolog.NET runtime to be installed on the target machine the program is being executed on. The Prolog.NET compiler uses the ILMerge utility from Microsoft Research to merge the .NET executable with the Prolog.NET runtime libraries
Calling .NET from Prolog
There are five built-in predicates in Prolog.NET that allow using .NET objects and methods from Prolog: object/2, invoke/3, get_property/3, set_property/3, and ::/2. This section describes how each can be used in a Prolog program. In order to use the object-oriented predicates against classes and objects in an assembly or namespace, the using/1 or assembly/1 directives should be specified. The using/1 predicate takes a .NET namespace as its only argument, while the assembly/1 takes an assembly name. They both load an assembly so that the class types defined in it can be used from Prolog. The following are examples of each:
When the Prolog.NET runtime environment searches for the class type to be instantiated, it first looks in the assemblies that have been loaded via using/1. If not found, it will then look in the assemblies loaded via assembly/1. To instantiate an object from .NET the object/1 built-in predicate is used:
The first argument is an atom of the class type which is created and bound to the second argument, known as an object term. The following is an example of creating an ArrayList in Prolog:
Calling a Class/Object method
There are two ways to call methods from Prolog, either using the invoke/3 or ::/2 built-in predicate. The advantage of the latter is that it provides a more OOP-familiar syntax and its method’s return value can be evaluated using the is/2 predicate.
The invoke/3 predicate takes three arguments. The first argument is the object term (or the class type name in case we are invoking a static method). The second argument is a functor representing the method name as well as aguments passed to it. Finally, the third term is unified with the value returned from invoked:
The following is an example of invoking the Add() method from an example Calculator class that we implemented ourselves.
Since the return value is unified with the last argument, querying the following two goals is similar:
Another way to invoke a method is by using the ::/2 built-in predicate, which has the following syntax:
For instance, Calling the Console.WriteLine() method the following way:
The ::/2 predicate can be used in an is/2 expression. For example, rew-writing the above dotnet_add/3 rule using ::/2 would look like:
You can get and modify that values of class properties, which are responsible for setting and getting non-public variables in a class. The two predicates available to achieve this are: get_property/3 and set_property/3. Both of these predicates are used almost the same way as the invoke/3 predicate. The predicate signatures are:
The first argument term of each predicate is the instantiated object or a class type (in case we are getting/setting a static property of a class). The second argument is the property name. The third argument in get_property/3 is unified with the value returned from the property, while in set_property/3, it’s the value that the property needs to be set to. The following is an example of getting a property that checks if a System.Collections.ArrayList object has no elements by asserting that the ArrayList.Count property returns 0:
The following is an example of using set_property to set the Max property of a Calculator class:
Built-In Prolog Predicates
The Prolog.NET library currently has a limited set of built-in predicates that are common to Prolog implementations. The following standard Prolog predicates are available:
Equality and Unification Predicates
.NET Interoperability Predicates