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.
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.
Entering a query:
At the prompt "?-" type in a one line Prolog query, as usual, for instance, something like:
?- write(hello),nl.
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.
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.
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.
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)) |
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.
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.
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.
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.
% 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).
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 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.
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.
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.
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.
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).
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.
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.
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.
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.
An example package in tarau/jinnigui is provided. Adding builtins is self explanatory once the programmer becomes aware of the following issues:
% 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