Kernel Prolog: a Lightweight Prolog-in-Java Interpreter with Fluent based Built-ins

USER GUIDE

Paul Tarau, November 1999

About Kernel Prolog

Kernel Prolog supports a simplified CUT-less and operatorless subset of ISO Prolog. Its main merit is a redesigned set of built-ins and an axiomatic reconstruction of Prolog on top of Horn Clauses with first class Fluents. Kernel Prolog Solvers (first class LD-resolution interpreters), together with Sources and Sinks provide stateful objects needed to represent the state of the Solvers as well as for interoperation with the embedding Object Oriented environment.

Getting started

Installation and essential files

Make sure you uncompress the provided *.zip or *.tar.gz file containing Kernel Prolog. The toplevel interaction starts with something like

java JinniMain

To run Kernel Prolog from another directory create a small batch file, kp.bat containing something like:

java -cp ".;c:/tarau/bp_dist/kprolog" JinniMain %1 %2 %3 %4 %5 %6 %7 %8 %9

in the case of JDK 1.2 on windows NT. You might need to adapt this with other versions of Java or on other platforms.
The Visual Kernel Prolog environment is started with gui.bat, calling something like

java tarau.jinnigui.JinniGuiMain ide

Quick info - if you want to try out things right away. Look at the various *.bat files for hints on how to start the various components.

The core of Kernel Prolog is in the Java classes inside the subdirectory tarau/jinni. Look at the Kernel Prolog class documentation in directory docs. The most important file, allowing you to configure/fix problems with Kernel Prolog is lib.pro - containing the definition of most built-ins implemented in Prolog. If you have the Kernel Prolog sources, the essential files to look at are Init.java,Prog.java, IO.java and Builtins.java in directory tarau/jinni and the UserBuiltins.java file showing how to extend Kernel Prolog with new Java operations in the main Jini directory. The subdirectory tarau.jinniguicontains  the sources and classes of Visual Kernel Prolog - an extension of Kernel Prolog covering some frequently used Java awt components.

Note: Kernel Prolog has been implemented as a trimmed down derivative of Jinni ( http://www.binnetcorp.com/Jinni ). Package and file names have been kept similar to those in Jinni for easier interoperation. However, both the basic Term tree, IO operations and the interpreter are new and significantly different from Jinni's. The Jinni GUI has been ported with minor changes.

Using Kernel Prolog in command line mode:


Entering a query:

At the prompt "?-" type in a one line Prolog query, as usual, for instance, something like:

?- write(hello),nl.

Consulting a new program:

?-[<myprog>].

will read in memory the file <myprog>.pro program replacing similarly named predicates with new ones. It is actually a shorthand for reconsult/1. To accumulate clause for similarly named predicates, use consult/1.

?-co.

will reconsult again the the last consulted file. If you start Kernel Prolog with its visual environment, try something like:

?-ide.

to lauch an instance of the IDE, or

?-[vq8],go.

to run a visual N-Queens program.
 

Using Kernel Prolog through an applet

Type appletviewer JinniGUI.html . Enter, for instance, in the query window:

append(Xs,Ys,[1,2,3]).

You will see the applet displaying the results. Applets can load files located at the site where they originate from. Try

?-[q8],queens(8,X)

to consult over the net and run the N-queens program. To launch an instance of a simple IDE with embedded editor and query processor, type

?-ide.
 

Building simple Prolog programs with Kernel Prolog

With the editor of your choice create a text file called simple.pro, with the following content:

c(X):-a(X),b(X).

a(1).
a(2).
a(3).

b(2).
b(3).
b(4).

In a shell window (DOS command, csh, bash etc.), type java JinniMain to launch the standalone command line Jinni interpreter, prompting you with "?- " to enter commands/queries. To load the file type:

?-[simple].

then try the query:

?-c(X).

Example of reading/writing to files:

Kernel Prolog can read files or URLs and write files in command line mode. In applet mode Kernel Prolog can only read files described as URLs, from from the directory it comes from. Try:

?-clause_file_writer('example.txt',File),fprint_clause(File,example(one)),stop(File).

followed by:

?-file_clause_reader('example.txt',File),fread(File,X),stop(File),println(X).

Jinni can open a URL over the net, in a way similar to file. Try:

?-file_char_reader('http://www.binnetcorp.com',Reader),source_list(Reader,Chars),put_codes(Chars),fail.

to print out a HTML file fetched directly from the Web.
 

LANGUAGE DEFINITION

Syntax

Unicode based language internationlization is supported by using only the (wide) char type and appropriate JDK 1.x reader and writer classes.
Kernel Prolog's parser and tokenizer are very small and based on Java's own built-in parsing/tokenizing libraries (StreamTokenizer). This allows relying on code already in  browsers instead of having to load classes over the network. In the future, as Kernel Prolog is heading towards plain Natural Language input through speech recognition software, it's internal syntax (which might already look too restrictive to Prolog programmers) will be generated through a preprocessor supporting user defined distfix operators. As such, kernel Prolog is expected to be able to emulate full Prolog syntax and more. However, Kernel Prolog's current syntax is restricted to standard prefix Prolog terms of the form f(T1,..Tn), with the infix operators ':-' and ',' accepted only at top level:

Clause ::                                 Head :- Body.
Clause ::                                 Head.
Head ::                                   Term
Body ::                                   CommaSeparatedTerms
CommaSeparatedTerms ::     Term {,Term}*
Term ::                                   Const | VARIABLES | CompoundTerm
CompoundTerm ::                  IDENTIFIER(CommaSeperatedTerms)
Const ::                                  IDENTIFIER  |  INTEGER  |  REAL

IDENTIFIERs starting with lower case characters and containing only alphabetical characters do not require quoting, for instance is(X,99) is a valid term. When IDENTIFIERs start with upper case or contain non-alphabetical characters they require single quotes, i.e. you should write something like ':-'(a(X),b(X)) or '+'(3,4). Variables start with upper case characters or "_". Both % one line and /* ... */ multiple line comments are supported. A way to run under full Prolog programs with trickier operator syntax is to write them to a file using plain term syntax.

While this syntax looks restrictive, in fact, it is enough for expressing everything Prolog semantics actually supports. The following table shows how to handle the differences:
 
 
Prolog: Jinni:
assert((b(X):-a(X),c(X))  assert(':-'(a(X),','(b(X),c(X))))
X is 3+4 is(X,'+'(3,4))

Some simple definitions like eq(X,X), and if/3 present in Jinni, allow alternative writings like:
 
Prolog: Jinni:
(X=1->Y=2;Y=3) if(eq(X,1),eq(Y,2),eq(Y,3))
(X=1->true;X=2) if(eq(X,1),true,eq(X,2))

Semantics

Kernel Prolog can be seen as a collection of Horn Clause Interpreters running LD-resolution on a default clause database and calling built-in operations. Through built-in calls, they possess the ability to create other query first class engines and interoperate with Java through a set of stateful objects called Fluents.

Fluents are created with specific constructors, usually named the name of the type, by converting from other fluents or conventional Prolog data structures like Terms, Lists or Databases. All Fluents are enabled with a stop/1  operation which releases their resources (most Fluents also call stop/1 on backtracking to free resources).

Sources are Fluents enabled with an extra get/2 operation. Typical Sources are Horn Clause Engines, File, URL or String Readers, Fluents built form Prolog lists. Sinks are fluents enabled with an extra put/2 and collect/2 operation. Typical fluents are ClauseWriters or CharWriters targeted to Files,  TermCollectors  (implemented as a Java Vectors collecting Prolog terms), StringSinks (implemented as a Java StringBuffers colecting String representations of Prolog terms). Prolog Databases are first class citizens implemented as extensions of Sources which provide add/2, remove/2, collect/2 operations.

Fluents are resources which go throuh state transitions as a result of  put/2 or get/2 operations. They end their life cycle in a stopped state when all the resources they hold are freed. An algebra of Fluent composers provides abstract operations on fluents. For instance, append_sources/3 creates a new Source with a get/2 operation such that when the first Source is stopped, iteration continues over the elements of the second source.   Compose_sources/3 provides a cartesian product style composition, the new get/2 operation returning pairs of elements of the first and second Source. Split_source/3 creates 2 Source objects identical to the Source given as first argument. It allows writing programs which iterate over a given Source multiple times. Sources and Sinks are related through a discharge(Source,Sink) operation which sends all the elements of the Source to the given Sink.
 

Programming without CUT

Kernel Prolog does not implement Prolog's CUT. With embedded if/3 constructs, not/1 and using distinct patterns returned as results from built-ins, to allow
distinct first argument based case analysis, someone can cover easily most uses of CUT. Using once(Goal) allows restricting your attention to the first solution of Goal, in a way similar to Goal,! in Prolog.

Some Classic Kernel Prolog built-in predicates

Kernel Prolog's built-in predicates follow the spirit of Prolog while simplifying when possible and adjusting to its restricted syntax. For their complete executable specification and typical use cases we refer to the file tarau/jinni/lib.pro containing the Kernel Prolog system library.

Basic Prolog built-ins:

Definitions of these and other built-ins are provided in file lib.pro.

Operations on Interpreters (instances of Source Operations)

File, socket, URL and string based stream operations (based on Source operations)

Jinni extends ISO Prolog's stream I/O based operations to URLs and strings - to give a uniform view of all of them as streams.

Output operations:

Input operations:

Database operations

Fluent Based Built-ins and Derived Operations

Fluent Constructors

Fluents are created with specific constructors, usually by converting from other Fluents or conventional Prolog data structures like Terms, Lists or Databases. 2. All Fluents are enabled with a stop/1 operation which releases their resources (most Fluents also call stop/1 on backtracking, through their internal undo operation).

In our Java based reference implementation, the Fluent class looks as follows:

class Fluent extends SystemObject {
  Fluent(Prog p) {
    trailMe(p);
  }

  // add the fluent to the parent Interpreter's Trail
  protected void trailMe(Prog p) {
    if(null!=p) p.getTrail().push(this);
  }

  // usable (through overriding) to release resources
  // and/or stop ongoing computations
  public void stop() {
  }

  // release resources on backtracking, if needed
  protected void undo() {
    stop();
  }
}

Sources are Fluents enabled with an extra get/2 operation. Typical Sources are Horn Clause Interpreters, File, URL or String Readers, Fluents built form Prolog lists, Fluents iterating over data structures like Vectors or Hashtables or Queues in the underlying implementation language.

Note that the constructor Fluent(Prog p) does a trailing operation on the caller program p's Trail, and provides and undo operation to be called by p on backtracking, to release resources through the Fluent's stop method.

In our Java based reference implementation the Source abstract class looks as follows:

abstract class Source extends Fluent {
  Source(Prog p) {
    super(p);
  }

  abstract public Term get();
}

Sinks are fluents enabled with an extra put/2 and collect/2 operation. Typical Sinks are ClauseWriters or CharWriters targeted to TermCollectors (implemented as a Java Vectors collecting Prolog terms), StringSinks (implemented as a Java StringBuffers collecting String representations of Prolog terms), Files.

In our Java based reference implementation the Sink abstract class looks as follows:

abstract class Sink extends Fluent {

  Sink(Prog p) {
    super(p);
  }

  // sends T to the Sink for tasks as
  // accumulation or printing
  abstract public int put(Term T);

  // return data previously sent to the Sink
  // (if collection ability is present)
  public Term collect() {
    return null;
  }
}

Not surprisingly, even Prolog databases are first class citizens implemented as extensions of Sources which provide add/2, remove/2, collect/2 operations.

Fluents can be seen as resources which go through state transitions as a result of put/2, get/2 and stop/1 operations. They end their life cycle in a stopped state when all the data structures and/or threads they hold are freed.
 

Fluent Composers

Fluent composers provide abstract operations on Fluents. They are usually implemented with lazy semantics.

For instance, append_sources/3 creates a new Source with a get/2 operation such that when the first Source is stopped, iteration continues over the elements of the second Source.

Compose_sources/3 provides a cartesian product style composition, the new get/2 operation returning pairs of elements of the first and second Source. Reverse_source/2 builds a new Source such that its get/2 method returns its elements in reverse order.

Split_source/3 creates two Source objects identical to the Source given as first argument. It allows writing programs which iterate over a given Source multiple times. Sources and Sinks are related through a discharge(Source,Sink) operation which sends all the elements of the Source to the given Sink.

Fluent Modifiers

Fluent modifiers allow dynamically changing some attributes of a give Fluent. For instance set_persistent(Fluent,YesNo) is used to make a Fluent survive failure, by disabling its undo method, which, by default, applies the Fluent's stop method on backtracking.

Interpreters as Answer Sources

Let us put to work in a more specific way the view of Interpreters as Fluents. All we have to do, is provide is a Fluent constructor, creating an Answer Source from an Answer Pattern and a Goal given to an Interpreter. As a result, we will cover negation, limited pruning through once/1, if-then-else/3, findall/3, var/1, and, beyond standard Prolog, forms of lazy, on-demand generation of sets of solutions, as well as a uniform set of built-ins for manipulation of first class Prolog databases and external objects like Files or URLs.

Answer Sources can be seen as generalized iterators, allowing a given program to control answer production in another. Each Answer Source works as a separate Horn Clause LD-resolution interpreter.

The Answer Source constructor initializes a new interpreter.

answer_source(AnswerPattern,Goal,AnswerSource)
creates a new Horn Clause solver, uniquely identified by AnswerSource (a Source Fluent), which shares code with the currently running program and is initialized with resolvent Goal. AnswerPattern is a term, usually a list of variables occurring in Goal.

The get/2 operation (provided by all Sources) is used to retrieve successive answers generated by an Answer Source, on demand.

get(AnswerSource,AnswerInstance)
tries to harvest the answer computed starting from Goal, as a instance of AnswerPattern. If an answer is found it is returned as the(AnswerInstance), otherwise no is returned. Note that once no has been returned, all subsequent get/2 on the same AnswerSource will return no. Bindings are not propagated to the original Goal or AnswerPattern when get retrieves an answer, i.e. AnswerInstance is obtained by first standardizing apart (renaming) the variables in Goal and AnswerPattern, and then backtracking over its alternative answers in a separate Prolog interpreter. Therefore, backtracking in the caller interpreter does not interfere with the new Answer Source's iteration over answers. Note however that backtracking over the Answer Source's creation point as such makes it unreachable and therefore subject to garbage collection.

Finally, an Answer Source is stopped with the stop/1 operation implemented by all Sources.

stop(AnswerSource)
The stop/1 operation is called automatically when no more answers can be produced as well as through the Fluent's undo operation on backtracking.

 Source level extensions through new definitions

To give a glimpse to the expressiveness of the resulting language, we will know introduce, through definitions in Kernel Prolog, a number of predicates known as `impossible to emulate' in Horn Clause Prolog (except by significantly lowering the level of abstraction and implementing something close to a Turing machine).

Negation and once/1

These constructs are implemented simply by discarding all but the first solution produced by a Solver.
% returns the(X) or no as first solution of G
first_solution(X,G,Answer):-
  answer_source(X,G,Solver),
  get(Solver,R),
  stop(Solver),
  eq(Answer,R).

% succeeds by binding G to its first solution or fails
once(G):-first_solution(G,G,the(G)).

% succeeds without binding G, if G fails
not(G):-first_solution(_,G,no).
 

Reflective Meta-Interpreters

The simplest meta-interpreter metacall/1 just reflects backtracking through element_of/2 over deterministic Answer Source operations.
metacall(Goal):-
  answer_source(Goal,Goal,E),
  element_of(E,Goal).
  
element_of(I,X):-get(I,the(A)),select_from(I,A,X).

select_from(_,A,A).
select_from(I,_,X):-element_of(I,X).
We can see metacall/1 as an operation which fuses two orthogonal language features provided by Answer Sources: computing an answer of a Goal, and advancing to the next answer, through the source level operations element_of/2 and select_from/3 which 'borrow' the ability to backtrack from the underlying interpreter.

Note that element_of/2 works generically on Sources and is therefore reusable, for instance, to backtrack over the character codes of a file or a URL.

We can now build a meta-interpreter which implements the transitive closure of the unfolding operation (provided as the get/2 operation of an Unfolder Source in the underlying implementation language), combined with backtracking trough element_of/2.

unfold_solve(Goal):-
  unfold(':-'(Goal,Goal),':-'(Goal,true)).

unfold(Clause,Clause).
unfold(Clause,Answer):-
  unfolder_source(Clause,Unfolder),
  element_of(Unfolder,NewClause),
  unfold(NewClause,Answer).

If-then-else

Once we have first solution and metacall operations, emulating if-then-else is easy.
% if Cond succeeds executes Then otherwise Else
if(Cond,Then,Else):-
  first_solution(successful(Cond,Then),Cond,R),
  select_then_else(R,Cond,Then,Else).

select_then_else(the(successful(Cond,Then)),Cond,Then,_):-Then.
select_then_else(no,_,_,Else):-Else.

All-solution predicates

All-solution predicates like findall/3 can be obtained simply as:

findall(X,G,Xs):-
   answer_source(X,G,Solver),
   source_list(Solver,Xs).

We also provide a direct implementation of Database Fluents, which reflect to object level the interpreter's own handling of the Prolog database. As an additional benefit, multiple databases are provided. See predicates starting with db_ for operations on databse fluents, in file lib.pro.

Lists and Terms as Source Fluents

Sequential Prolog data structures are mapped to Fluents naturally. For instance, list_source/2 creates a new Fluent based on a List, such that its get/2 operation will return one element of the list at a time. Similarly term_source/2 creates a Fluen from an N-argument compound term, such that its get/2 method will return firs its function symbol then each argument. These built-ins are usable to emulate conventional Prolog operations like univ/2 (also known as =..) quite easily:
univ(T,FXs):-if(var(T),list_to_fun(FXs,T),fun_to_list(T,FXs)).

list_to_fun(FXs,T):-list_source(FXs,I),source_term(I,T).
fun_to_list(T,FXs):-term_source(T,I),source_list(I,FXs).
As they can be converted easily to/from Prolog data-structures, Fluents are usable as canonical representation for data objects as well as for computational processes (like in the case of answer_sources). Note that they also allow fast iteration using loops over efficient native data structures in the implementation language instead of recursion in the object language. Clearly, interfacing with external objects is also made simpler by this fact.
 

Arithmetics

Arithmetics through built-in and user defined functions

Note that one can assume, at an abstract level, that arithmetics is part of Kernel Prolog through the usual extension of Horn Clauses with successor arithmetics.

Kernel Prolog provides a unique built-in for handling arithmetic functions,

  compute(Operation,Arg1,Arg2,Result)
An enhanced is/2 evaluator, supporting execution of arbitrary user defined functions of N arguments provided as N+1 argument Prolog relations, is implemented at source level.

Lazy Arithmetics with Fluents

Arithmetic operations producing sequences like random number generators, primes, arithmetic or geometric series etc. can be implemented efficiently in the underlying language and provided in Kernel Prolog as fluents. Our reference implementation provides a generic
  integer_source(MaxFuel,A,X,B).
built-in operation allowing iterated computation of X<-A*X+B at most MaxFuel times (or an infinite sequence if MaxFuel = 0). Building specialized arithmetic operations on top of this is quite easy:

% works with From =< To as well as with To =< From
counter(From,To,Counter):-
  compute('-',To,From,Dif),
  compute('?',To,From,Sign),
  compute('*',Dif,Sign,N),
  compute('+',N,1,Steps),
  integer_source(Steps,1,From,Sign,Counter).

for(I,From,To):-
  counter(From,To,Counter),
  element_of(Counter,I).

ints(From,To,Ns):-
  counter(From,To,Counter),
  source_list(Counter,Ns).

ints(Max,Ns):-ints(1,Max,Ns).
 

Memoing Fluents

Most Fluents are designed to be usable only once, by default, and release all resources held (automatically on backtracking or under programmer's control when their stop operation is invoked). While Fluent operations like split_fluent/3 can be used to duplicate most Source Fluents, the following alternative provides a more efficient alternative.

A Memoing Fluent is built easily on top of a Source Fluent by accumulating values in a List or dynamic array. A Memoing Fluent can be shared between multiple consumers which want to avoid recomputation of a given value.

Fluent based Lazy Lists

Lazy Lists can be seen as an instance of Memoing Fluents: they accumulate successive values of a Source Fluent in a (reusable) list. The simple Lazy List abstraction in our reference implementation works as follows:
  source_lazy_list(Source, LazyList)
creates a new LazyList object form a Source object:
  lazy_head(LazyList, LazyHead)
extracts the current head element of the list. Iteration over the list is provided by
  lazy_tail(LazyList, LazyTail)
which returns LazyTail, a new lazy list encapsulating the next stage of the Source fluent.

While complete automation of lazy lists through a form of attributed variable construct is possible, we have chosen a simpler implementation scenario based on the previously described operations, mainly because overriding unification with execution of an arbitrary procedure would introduce potential non-termination - something which would break the very idea of keeping the execution mechanism as close as possible to basic Horn Clause resolution, as available in classic Prolog.

Based on these operations, a lazy findall/3 is simply:

% creates lazy list form an answer source
lazy_findall(X,G,LazyList):-
  answer_source(X,G,S),
  source_lazy_list(S,LazyList).
In fact, the behavior of the lazy list encapsulating lazy_findall's advancement on alternative solutions produced by an Answer Source, is indistinguishable from a lazy list constructed from an ordinary list_source:
% creates a lazy list from a lazy_list(List,LazyList):-
  list_source(List,S),
  source_lazy_list(S,LazyList).
The following operations will produce a lazily growing reusable list, to be explored with lazy_element_of/2 in a way similar ordinary lists are explored with member/2.
% explores a lazy list in a way compatible with backtracking
% allows multiple 'consumers' to access the list, end ensures that
% the lazy list advances progressively and consistently

lazy_element_of(XXs,X):-
  lazy_decons(XXs,A,Xs),
  lazy_select_from(Xs,A,X).

% backtracks over the lazy list
lazy_select_from(_,A,A).
lazy_select_from(XXs,_,X):-lazy_element_of(XXs,X).

% returns a head/tail pair of a non-empty lazy list
lazy_decons(XXs,X,Xs):-
  neq(XXs,[]),
  lazy_head(XXs,X),
  lazy_tail(XXs,Xs).
A minor change on Prolog's chronological backtracking is needed however: only the creation point of the lazy list is subject to trailing, and the complete lazy list is discarded at once. This is achieved easily in our reference implementation by giving to each lazy list its own (dynamically growing) trail, and by providing an undo operation which rewinds the trail completely when backtracking passes the lazy list object's creation point.

Multi Variables and Fluent based DCGs

Multi-Variables are special Fluents which accumulate multiple values on an internal stack. As the stack is popped on backtracking multi-variables return to their previous values, therefore providing a form of backrackable destructive assignment. A new Multi-Variable is built with def(MultiVar,InitialValue), it is uodated with set(MultiVar,NewValue) and its current value is retrived with val(MultiVar,Value)).

Among its applications, multi-stream DCGs, following BinPtrolog's Assumption Grammars implementation model, which does not require a DCG preprocessor, but uses backrackable destructive assignment instead, for advancing the state of a given DCG stream.

dcg_def(MultiVar,Xs):-def(MultiVar,Xs).
dcg_val(MultiVar,Xs):-val(MultiVar,Xs).
dcg_connect(MultiVar,X):-val(MultiVar,[X|Xs]),set(MultiVar,Xs).
The resulting implementation initializes an input list with dcg_def/2, retrieves its current value with dcg_val/2 and advances with the dcg_connect/2 relation, which consumes/generates a terminal symbol each time is called.

We refer to the file tarau/jinni/lib.pro for the precise semantics of Jinni built-ins and some other, less frequently used built-ins available in Jinni.

Calling Kernel Prolog from Java


A simple String to String function askJinni can be used to call Jinni from Java. Make sure you initialize Jinni first with something like Init.run(null). The string s returned by something like String s=Init.askJinni("eq(X,1)"); can be processed with Java String functions quite easily to extract the field containing the relevant answer. The following methods, from class Init.java, provide Term to Term functions for more efficient  Java to Jinni calls, not going through String representations.

  /**
    Asks a quert\y to return Answer such tha Goal succeeds and returns the
    first solution of the form "the(Answer)" or the constant  "no" if no solution exists
  */
  public static Term askJinni(Term Answer,Term Goal) {
    return Prog.fg(Answer,Goal);
  }

  /**
    Asks Jinni a query Goal and returns the
    first solution of the form "the(Answer)" , where
    Answer is an instance of Goal or the constant
    "no" if no solution exists
  */
  public static Term askJinni(Term Goal) {
    return askJinni(Goal,Goal);
  }

Look examples of their use  in some Java to Jinni calls in the GUI subdirectory, implementing Visual Jinni.
 

Calling Java from Kernel Prolog

Calling Java from Kernel Prolog happens through instances of FunBuiltin and ConstBuiltin. Each builtin has to be registered, allowing Kernel Prolog to
dynamically integrate it into its runtime system.

An example package in tarau/jinnigui is provided. Adding builtins is self explanatory once the programmer becomes aware of the following issues:

Take a look at the Java files in directory tarau/jinnigui for more examples of built-ins, and read the next section for their description.
 

Visual Kernel Prolog

Visual Kernel Prolog is a GUI library built by extending Jinni with user defined built-ins and Java to Prolog calls associated to Java events. This enables Prolog  to work as a high-level Visual scripting language. For instance, the following script (part of the script library gui_lib.pro) shows how to customize a simple editor component, running in its own frame (window).

% creates a default editor
edit:-edit(50,50).

% creates editor component in new frame positioned at WhereX, WhereY
edit(WhereX,WhereY):-
  new_frame('Jinni Editor Component',1,1,F),
  move(F,WhereX,WhereY),
  edit_in(F),
  show(F).

Let us walk through a complet example, defining a Jinni button with an associated action defined as Prolog code. First, we create a subclass of Button, customized for our needs, called JinniButton.

/**
   Button with attached Jinni action.
   Runs action on new thread, when Button pushed.
*/
class JinniButton extends Button implements Runnable {
  JinniButton(String name,Term action) {
    super(name);
    this.name=name;
    this.action=action; // copy() called in corresponding builtin
    prog=null;
  }

  private String name;
  private Term action;
  private Prog prog;

  /**
     Passes action to Jinni when Button is pushed
  */
  public void run() {
    if(prog!=null) prog.stop();
    prog=Prog.bg(action); //runs goal
  }
}

Note that JinniButton implements Runnable. It will trigger an action on its own Jinni thread - started with Prog.bg(action), where action is a fresh copy of a Jinni goal, passed to the Button by its constructor.

/**
  new_button(JinniContainer,Name,Action,Button):
  creates a Button with label Name
  and attaches to it an action Action
*/
class new_button extends UserFunBuiltin {
  public new_button() {super(4);}

  // arg 0=container, arg 1=name, arg 2=action, arg 3=button
  public int exec(Prog p) {
    JinniContainer C =(JinniContainer)(((JavaObject)getArg(0)).toObject());
    String name=getArg(1).toUnquoted();
    JinniButton JB=new JinniButton(name,getArg(2).copy());
    C.add_it(JB);
    return putArg(3,new JavaObject(JB),p);
  }
}

Note  that a JavaObject is returned - this can be later used as an argument - for instance in can be colored, resized etc. The event handling mechanism of Jinni, while compatible with Java 1.02, is somewhat similar with later Java event models - in fact,  it is, arguably, more generic. It uses the following simple trick: when the event target is an instance of Runnable, our event handling mechanism just calls its run method. This allows defining only one action handler in the top level window (frame), which will dispatch events in a generic way, to various Runnable components. The code is as follows:

// action attached to a frame (top level window)

 public boolean action(Event evt, Object arg) {
      if(evt.target instanceof Runnable) {
        new Thread((Runnable)evt.target).start();
      }
      else {
        IO.println("UNEXPECTED  TARGET: "+evt.target);
        return false;
      }
      return true;
   }
 

For more information, read the White Paper on the Design and Reference Implementation of Kernel Prolog in HTML or PostScript form.

Copyright (c) BinNet Corp 1999, and Paul Tarau, 1999