This week I finished the python script that handles initialization, user input, and all messaging to & from Prolog. All that is left to do is the natural language parser component in Prolog.
I was learning Python as I wrote this, so a lot of this code could be certainly written in a more elegant way. But it gets the job done.
The biggest obstacle was a silly grammatical problem I encountered was interpreting the Prolog list of code to be executed into Python code. A typical prolog code result arrives in list format [forward, ‘1.0’, ‘3.0’]. I was trying to parse that into Python as forward(1.0, 3.0). Notice the lack of string quotes around Prolog’s forward; this is because in Prolog, its data type is “atom” rather than “string”. But this caused no end of trouble when pulling it into Python, as it was interpreting forward as a functor rather than as a string. No problem, right? It should be easy to just cast it as a string in Python. But despite all my efforts, it wouldn’t work… casting it was yielding a big mess, since it was interpreting it as a function pointer. I ended up having to resolve this issue on the Prolog side with a bit of a hack to get quotes around outgoing atoms.
Anyways, here is the Python code. The main routine parses user input, passes it to Prolog, and then checks for a solution and returns a PrologResult instance with fields prologcall, status, and pythoncode. Based on the result of status, it then executes pythoncode.
A typical command of “scribbler move forward” might yield the following PrologResult:
- prologCall = parseToCode([scribbler,move,forward],Status,CodeOut).
- status = valid
- pythoncode = [forward(3.0, 1.0)]
# File: scribpro.py # by Justin Mangue # # Description: # This is a script that enables use of a Prolog natural language parsing component to control a Scribbler II robot # over bluetooth. Use in sync with nlp.pl. # # Dependencies: # Python 2.7.4 http://www.python.org/getit/ # PySwip 0.2.3 https://code.google.com/p/pyswip/ # Myro 2.9.5 http://myro.roboteducation.org/download/ # Swi-Prolog 6.2.6 http://www.swi-prolog.org/Download.html from sys import exit from myro import * from pyswip import Prolog # SETTINGS verNum = '0.1' # ScribPro Version Number scribblerPort = 'COM5' # Scribbler Bluetooth Port prologFile = 'nlp.pl' # Prolog file to consult # GLOBALS prolog = Prolog() # Instantiation of Prolog interface # CLASSES class PrologResult(object): def __init__(self, prologcall, status, pythoncode): self.prologcall = prologcall self.status = status self.pythoncode = pythoncode # FUNCTIONS # strClean(str) -- converts "A string like This" into a list of lowercase atoms i.e. [a,string,like,this] for use in Prolog def strClean(string): finalstring = "[" loweredstring = string.lower() for ch in loweredstring: if ch == ' ': finalstring = finalstring + "," elif ch == '.': pass else: finalstring = finalstring + ch return finalstring + "]" # parse(str) -- cleans the user input and then throws it at Prolog for analysis def parse(inStr): sentence = strClean(inStr) prologCall = 'parseToCode(' + sentence + ', Status, CodeOut).' for soln in prolog.query(prologCall, maxresult=1): statusOut = ''.join(soln["Status"]) codeOut = soln["CodeOut"] for i in codeOut: if type(i) is list: listoflists = 1 else: listoflists = 0 if listoflists == 1: codeOut = buildMultiCode(codeOut) else: codeOut = buildCode(codeOut) return PrologResult(prologCall,statusOut,codeOut) # buildCode([str,t1,t2,...]) -- converts a [instruction, param1, param2, ...] list into # Python-exec-friendly "instruction(param1,param2,...)" functor format def buildCode(prologterms): parameters = [str(item) for item in prologterms] if len(parameters) > 2: codeOut = parameters + '(' for x in range(len(parameters)-2): codeOut = codeOut + parameters[x+1] + ', ' codeOut = codeOut + parameters[len(parameters)-1] + ')' elif len(parameters) == 2: codeOut = parameters + '(' + parameters + ')' else: codeOut = parameters + '()' return codeOut # buildMultiCode([[str,t1,t2,...]]) -- converts a list of [instruction, param1, param2] codes into a list of # Python-exec-friendly "instruction(param1,param2,...)" strings # i.e. buildMultiCode([('forward',['1.0','3.0']),('stop',)]) -> ['forward(1.0, 3.0)', 'stop()'] def buildMultiCode(instructions): multiCodeOut =  for x in instructions: line = buildCode(x) multiCodeOut.append(line) return multiCodeOut # execute([str]) -- executes multiple lines of python script in sequence def execute(code): # potential security concerns here! if type(code) is list: for line in code: exec line else: exec code # MAIN def main(): # initialize the system print "*** ScribPro v" + verNum + " ***" init(scribblerPort) print "Scribbler II found on bluetooth (" + scribblerPort + ")." print "Prolog has been initialized." prolog.consult(prologFile) print "Loaded " + prologFile + " into SWI-Prolog!n" # main routine while(1): inStr = raw_input("Enter a command: ") if (inStr == 'quit' or inStr == 'exit'): print "Exiting..." exit(0) if inStr == 'reload': prolog = Prolog() prolog.consult(prologFile) Result = parse(inStr) print "Prolog query executed:", Result.prologcall print "Status:", Result.status if Result.status == 'valid': print "CodeOut:", Result.pythoncode execute(Result.pythoncode)