515 lines
14 KiB
Plaintext
515 lines
14 KiB
Plaintext
YPOS: YieldProlog for OpenSim
|
|
|
|
a compiler from Prolog to OpenSim compatible C# scripts
|
|
|
|
Ported by Kino Coursey and Douglas Miles at Daxtron Labs.
|
|
Based on Jeff Thompson Yield Prolog, http://yieldprolog.sourceforge.net/
|
|
For Prolog see http://en.wikipedia.org/wiki/Prolog
|
|
|
|
|
|
INTRODUCTION
|
|
|
|
This folder contains code to implement a Prolog compiler using the "Yield Statement" found in C#, Javascript, and Python.
|
|
The original Yield Prolog system can transform Prolog programs into C# code.
|
|
In this system we detect and extract YieldProlog code (with "//YP" as the first four characters in the script) and seperate it from any c# code ("marked by "//CS").
|
|
The YP code is transformed to C# and prepended to the "//CS" section, and passed as a bundel to the existing C# compiler.
|
|
The end result is Prolog can interface to OpenSim using the existing "//CS" functionality, and C# can call the compiled Prolog.
|
|
As such YP allows both declaritive and procedural programming in a 3D script enabled environment.
|
|
|
|
FEATURES
|
|
* Allows implementation of logic programming for objects and agents.
|
|
* C#/Javascript/Python as intermediate language
|
|
* Yield Prolog has relatively high speed of execution which is important in OpenSim. http://yieldprolog.sourceforge.net/benchmarks.html
|
|
* It is compatable with the existing C#/Mono based system.
|
|
* Yield Prolog is BSD license
|
|
* Calling Prolog from C# scripts
|
|
* Calling C# functions (with LSL and OS functions) from Prolog
|
|
* Prolog dynamic database
|
|
* Edinburgh, Cocksin & Mellish style syntax.
|
|
* Compiler is generated by compiling the Prolog descrition of itself into C#
|
|
* Same script entry interface as LSL
|
|
|
|
* Yield Prolog 1.0.1 Released : it passes all but 9 of the 421 tests in the ISO Prolog test suite (97.8%).
|
|
|
|
TODO
|
|
* Utilize ability to generate Javascript and Python code
|
|
* Integrate Prolog database with Sim
|
|
* Translation error reporting back to the editor
|
|
* Communications via message passing
|
|
* Interface to external inference engines
|
|
|
|
POSSIBILITIES
|
|
* Inworld expert systems
|
|
* Parallel logic programming and expert systems
|
|
* Ontology based processing
|
|
* Knowledge based alerting, accessing and business rules
|
|
For instance, listen on channel x, filter the events and broadcast alerts on channel y
|
|
or send IM, emails etc.
|
|
|
|
|
|
USAGE:
|
|
|
|
Add "yp" as an allowed compiler
|
|
|
|
OpenSim.ini
|
|
[ScriptEngine.DotNetEngine]
|
|
AllowedCompilers=lsl,cs,js,vb,yp
|
|
|
|
Enter scripts using the inworld editing process. Scripts have the following format.
|
|
The first line of a file must have "//yp".
|
|
|
|
//yp
|
|
<PROLOG CODE>
|
|
//CS
|
|
<CS CODE>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
C# code calling a Prolog Predicate:
|
|
-----------------------------------
|
|
The Prolog predicate is transformed into a C# boolean function. So the general calling format is:
|
|
foreach( bool var in prolog_predicate(ARGS)) {};
|
|
|
|
I/O is via using a string reader and writer in conjunction with YP.See() and YP.Tell()
|
|
|
|
StringWriter PrologOutuput= new StringWriter();
|
|
StringReader PrologInput= new StringReader(myInputString);
|
|
YP.see(PrologInput);
|
|
YP.tell(PrologOutuput);
|
|
<CALL PROLOG CODE HERE>
|
|
YP.seen();
|
|
YP.told();
|
|
StringBuilder builder = PrologOutput.GetStringBuilder();
|
|
string finaloutput = builder.ToString();
|
|
|
|
Any prolog reads and writes will be to the passed StringReader and StringWriter. In fact any TextReader/TextWriter class can be used.
|
|
|
|
Strings in Prolog are stored as Atom's and you need to use an Atom object to match.
|
|
|
|
\\yp
|
|
wanted('bob').
|
|
\\cs
|
|
string Who="bob";
|
|
foreach( bool ans in wanted(Atom.a(Who) )){};
|
|
|
|
|
|
Prolog code calling a C# function:
|
|
-----------------------------------
|
|
The prolog code uses the script_event('name_of_function',ARGS) builtin, which is transformed into the function call.
|
|
The C# function called uses "PrologCallback" and returns a boolean.
|
|
|
|
|
|
|
|
Dynamic database assertions:
|
|
-----------------------------------
|
|
|
|
void assertdb2(string predicate, string arg1, string arg2)
|
|
{
|
|
name = Atom.a(predicate);
|
|
YP.assertFact(name, new object[] { arg1, arg2 });
|
|
}
|
|
|
|
void retractdb2(string predicate, string arg1, string arg2)
|
|
{
|
|
name = Atom.a(predicate);
|
|
YP.retractFact(name, new object[] { arg1, arg2 });
|
|
}
|
|
|
|
----------- IMPORT EXTERNAL FUNCTIONS ----------
|
|
Using 'import' to call a static function
|
|
|
|
Taken mostly from http://yieldprolog.sourceforge.net/tutorial4.html
|
|
|
|
If we want to call a static function 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).
|
|
|
|
The import directive has two arguments.
|
|
The first argument is the module where the imported function is found, which is always ''.
|
|
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.
|
|
|
|
Note: you can use an imported function in a dynamically defined goal, or a function in another class.
|
|
|
|
:- import('', [parent/2]).
|
|
uncle(Person, Uncle) :- Goal = parent(Person, Parent), Goal, brother(Parent, Uncle).
|
|
|
|
:- import('', ['OtherClass.parent'/2]).
|
|
uncle(Person, Uncle) :- 'OtherClass.parent'(Person, Parent), brother(Parent, Uncle).
|
|
|
|
--------- Round-about Hello Wonderful world ----------
|
|
//yp
|
|
:-import('',[sayit/1]).
|
|
sayhello(X):-sayit(X).
|
|
|
|
//cs
|
|
public void default_event_state_entry()
|
|
{
|
|
llSay(0,"prolog hello.");
|
|
foreach( bool ans in sayhello(Atom.a(@"wonderful world") )){};
|
|
}
|
|
|
|
PrologCallback sayit(object ans)
|
|
{
|
|
llSay(0,"sayit1");
|
|
string msg = "one answer is :"+((Variable)ans).getValue();
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
------------------ UPDATES -----------------
|
|
Yield Prolog 1.0 Released : It passes all but 15 of the 421 tests in the ISO Prolog test suite.
|
|
|
|
New Features:
|
|
* Added support for Prolog predicates read and read_term.
|
|
* In see, Added support for a char code list as the input.
|
|
Using this as the input for "fred" makes
|
|
set_prolog_flag(double_quotes, atom) and
|
|
set_prolog_flag(double_quotes, chars) pass the ISO test suite.
|
|
|
|
Fixed Bugs:
|
|
* In atom_chars, check for unbound tail in the char list.
|
|
This makes atom_chars pass the ISO test suite.
|
|
* In current_predicate, also check for static functions.
|
|
This makes current_predicate pass the ISO test suite.
|
|
|
|
Known Issues:
|
|
|
|
Here are the 9 errors of the 421 tests in the ISO test suite in
|
|
YieldProlog\source\prolog\isoTestSuite.P .
|
|
Some of these have a good excuse for why Yield Prolog produces the error. The rest will be addressed in a future maintenance release.
|
|
|
|
Goal: call((fail, 1))
|
|
Expected: type_error(callable, (fail, 1))
|
|
Extra Solutions found: failure
|
|
|
|
Goal: call((write(3), 1))
|
|
Expected: type_error(callable, (write(3), 1))
|
|
Extra Solutions found: type_error(callable, 1)
|
|
|
|
Goal: call((1; true))
|
|
Expected: type_error(callable, (1 ; true))
|
|
Extra Solutions found: type_error(callable, 1)
|
|
|
|
Goal: (catch(true, C, write('something')), throw(blabla))
|
|
Expected: system_error
|
|
Extra Solutions found: unexpected_ball(blabla)
|
|
|
|
Goal: catch(number_chars(A,L), error(instantiation_error, _), fail)
|
|
Expected: failure
|
|
Extra Solutions found: instantiation_error
|
|
|
|
Goal: Goal: (X = 1 + 2, 'is'(Y, X * 3))
|
|
Expected: [[X <-- (1 + 2), Y <-- 9]]
|
|
Extra Solutions found: type_error(evaluable, /(+, 2))
|
|
|
|
Goal: 'is'(77, N)
|
|
Expected: instantiation_error
|
|
Extra Solutions found: N <-- 77)
|
|
|
|
Goal: \+(!, fail)
|
|
Expected: success
|
|
Extra Solutions found: failure
|
|
|
|
((X=1;X=2), \+((!,fail)))
|
|
Expected: [[X <-- 1],[X <-- 2]]
|
|
Extra Solutions found: failure
|
|
|
|
|
|
|
|
|
|
|
|
========================= APPENDIX A: touch test ================================
|
|
|
|
|
|
===================================
|
|
Input YP Code
|
|
===================================
|
|
//yp
|
|
mydb('field2','field1').
|
|
mydb('andy','jane').
|
|
mydb('carl','dan').
|
|
mydb('andy','bill').
|
|
mydb('andy','betty').
|
|
|
|
call_me(X):-mydb(X,Y) , respond(Y).
|
|
respond(X):- script_event('sayit',X).
|
|
|
|
//cs
|
|
public void default_event_touch_start(int N )
|
|
{
|
|
llSay(0,"pstart1");
|
|
foreach( bool ans in call_me(Atom.a(@"andy") )){};
|
|
llSay(0,"pstop2");
|
|
}
|
|
|
|
public void default_event_state_entry()
|
|
{
|
|
llSay(0,"prolog tester active.");
|
|
}
|
|
|
|
PrologCallback sayit(object ans)
|
|
{
|
|
llSay(0,"sayit1");
|
|
string msg = "one answer is :"+((Variable)ans).getValue();
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
|
|
===================================
|
|
Generated CS Code
|
|
===================================
|
|
using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog;using OpenSim.Region.ScriptEngine.Common; using System.Collections.Generic;
|
|
namespace SecondLife { public class Script : OpenSim.Region.ScriptEngine.Common.ScriptBaseClass {
|
|
static OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog.YP YP=null;public Script() { YP= new OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog.YP(); }
|
|
//cs
|
|
public void default_event_touch_start(int N )
|
|
{
|
|
llSay(0,"pstart1");
|
|
foreach( bool ans in call_me(Atom.a(@"carl") )){};
|
|
llSay(0,"pstop2");
|
|
}
|
|
|
|
public void default_event_state_entry()
|
|
{
|
|
llSay(0,"prolog tester active.");
|
|
}
|
|
|
|
public IEnumerable<bool> sayit(object ans)
|
|
{
|
|
llSay(0,"sayit1");
|
|
string msg = "one answer is :"+((Variable)ans).getValue();
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
|
|
//YPEncoded
|
|
public IEnumerable<bool> mydb(object arg1, object arg2) {
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"carl"))) {
|
|
foreach (bool l3 in YP.unify(arg2, Atom.a(@"dan"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"andy"))) {
|
|
foreach (bool l3 in YP.unify(arg2, Atom.a(@"bill"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"andy"))) {
|
|
foreach (bool l3 in YP.unify(arg2, Atom.a(@"betty"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<bool> call_me(object X) {
|
|
{
|
|
Variable Y = new Variable();
|
|
foreach (bool l2 in mydb(X, Y)) {
|
|
foreach (bool l3 in respond(Y)) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<bool> respond(object X) {
|
|
{
|
|
foreach (bool l2 in this.sayit( X)) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
} }
|
|
|
|
|
|
|
|
========================= APPENDIX B:SENSOR INFORMED SCRIPT =====================
|
|
|
|
|
|
===================================
|
|
Input YP Code
|
|
===================================
|
|
//yp
|
|
|
|
nop.
|
|
|
|
good('Daxxon Kinoc').
|
|
good('Fluffy Kitty').
|
|
|
|
bad('Eric Evil').
|
|
bad('Spikey Plant').
|
|
|
|
prolog_notify(X) :- good(X) , script_event('accept',X).
|
|
prolog_notify(X) :- bad(X) , script_event('reject',X).
|
|
|
|
|
|
//cs
|
|
|
|
public void default_event_state_entry()
|
|
{
|
|
llSay(0,"prolog sensor tester active.");
|
|
|
|
// Start a sensor looking for Agents
|
|
llSensorRepeat("","",AGENT, 10, PI,20);
|
|
|
|
}
|
|
|
|
public void default_event_sensor(int number_detected )
|
|
{
|
|
int i;
|
|
for(i=0;i< number_detected ;i++)
|
|
{
|
|
string dName = llDetectedName(i);
|
|
string dOwner = llDetectedName(i);
|
|
foreach(bool response in prolog_notify(Atom.a(dName)) ){};
|
|
foreach(bool response in prolog_notify(dOwner) ){};
|
|
llSay(0,"Saw "+dName);
|
|
}
|
|
}
|
|
|
|
string decodeToString(object obj)
|
|
{
|
|
if (obj is Variable) { return (string) ((Variable)obj).getValue();}
|
|
if (obj is Atom) { return (string) ((Atom)obj)._name;}
|
|
return "unknown type";
|
|
}
|
|
|
|
PrologCallback accept(object ans)
|
|
{
|
|
string msg = "Welcoming :"+decodeToString(ans);
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
PrologCallback reject(object ans)
|
|
{
|
|
string msg = "Watching :"+decodeToString(ans);
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
|
|
===================================
|
|
Generated CS Code
|
|
===================================
|
|
|
|
using OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog; using OpenSim.Region.ScriptEngine.Common; using System.Collections.Generic;
|
|
namespace SecondLife { public class Script : OpenSim.Region.ScriptEngine.Common.ScriptBaseClass {
|
|
static OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog.YP YP=null; public Script() { YP= new OpenSim.Region.ScriptEngine.DotNetEngine.Compiler.YieldProlog.YP(); }
|
|
//cs
|
|
|
|
public void default_event_state_entry()
|
|
{
|
|
llSay(0,"prolog sensor tester active.");
|
|
|
|
// Start a sensor looking for Agents
|
|
llSensorRepeat("","",AGENT, 10, PI,20);
|
|
|
|
}
|
|
|
|
public void default_event_sensor(int number_detected )
|
|
{
|
|
int i;
|
|
for(i=0;i< number_detected ;i++)
|
|
{
|
|
string dName = llDetectedName(i);
|
|
string dOwner = llDetectedName(i);
|
|
foreach(bool response in prolog_notify(Atom.a(dName)) ){};
|
|
foreach(bool response in prolog_notify(dOwner) ){};
|
|
llSay(0,"Saw "+dName);
|
|
}
|
|
}
|
|
|
|
string decodeToString(object obj)
|
|
{
|
|
if (obj is Variable) { return (string) ((Variable)obj).getValue();}
|
|
if (obj is Atom) { return (string) ((Atom)obj)._name;}
|
|
return "unknown type";
|
|
}
|
|
|
|
public IEnumerable<bool> accept(object ans)
|
|
{
|
|
string msg = "Welcoming :"+decodeToString(ans);
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
public IEnumerable<bool> reject(object ans)
|
|
{
|
|
string msg = "Watching :"+decodeToString(ans);
|
|
llSay(0,msg);
|
|
yield return false;
|
|
}
|
|
|
|
|
|
//YPEncoded
|
|
public IEnumerable<bool> yp_nop_header_nop() {
|
|
{
|
|
yield return false;
|
|
}
|
|
}
|
|
|
|
public IEnumerable<bool> good(object arg1) {
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"Daxxon Kinoc"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"Fluffy Kitty"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<bool> bad(object arg1) {
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"Eric Evil"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
{
|
|
foreach (bool l2 in YP.unify(arg1, Atom.a(@"Spikey Plant"))) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public IEnumerable<bool> prolog_notify(object X) {
|
|
{
|
|
foreach (bool l2 in good(X)) {
|
|
foreach (bool l3 in this.accept( X)) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
{
|
|
foreach (bool l2 in bad(X)) {
|
|
foreach (bool l3 in this.reject( X)) {
|
|
yield return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} }
|
|
|
|
|