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 //CS 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); 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.BuiltIn_Commands_BaseClass { 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 sayit(object ans) { llSay(0,"sayit1"); string msg = "one answer is :"+((Variable)ans).getValue(); llSay(0,msg); yield return false; } //YPEncoded public IEnumerable 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 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 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.BuiltIn_Commands_BaseClass { 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 accept(object ans) { string msg = "Welcoming :"+decodeToString(ans); llSay(0,msg); yield return false; } public IEnumerable reject(object ans) { string msg = "Watching :"+decodeToString(ans); llSay(0,msg); yield return false; } //YPEncoded public IEnumerable yp_nop_header_nop() { { yield return false; } } public IEnumerable 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 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 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; } } } } } }