Week 6: Finishing the Python/Prolog interface

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

verNum = '0.1'              # ScribPro Version Number
scribblerPort = 'COM5'      # Scribbler Bluetooth Port
prologFile = 'nlp.pl'       # Prolog file to consult

prolog = Prolog()           # Instantiation of Prolog interface

class PrologResult(object):
  def __init__(self, prologcall, status, pythoncode):
     self.prologcall = prologcall
     self.status = status
     self.pythoncode = pythoncode


# 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 == '.':
            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
                listoflists = 0
        if listoflists == 1:
            codeOut = buildMultiCode(codeOut)
            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[0] + '('
        for x in range(len(parameters)-2):
            codeOut = codeOut + parameters[x+1] + ', '
        codeOut = codeOut + parameters[len(parameters)-1] + ')'
    elif len(parameters) == 2:
        codeOut = parameters[0] + '(' + parameters[1] + ')'
        codeOut = parameters[0] + '()'
    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)
    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
        exec code

def main():
    # initialize the system
    print "*** ScribPro v" + verNum + " ***"
    print "Scribbler II found on bluetooth (" + scribblerPort + ")."
    print "Prolog has been initialized."
    print "Loaded " + prologFile + " into SWI-Prolog!n"

    # main routine
        inStr = raw_input("Enter a command: ")
        if (inStr == 'quit' or inStr == 'exit'):
            print "Exiting..."
        if inStr == 'reload':
            prolog = Prolog()
        Result = parse(inStr)
        print "Prolog query executed:", Result.prologcall
        print "Status:", Result.status
        if Result.status == 'valid':
            print "CodeOut:", Result.pythoncode
About the Author: Justin
A 34 year old Software Engineer in Seattle, WA with a love for coding, music, video games, and the great outdoors.
Author Website: http://www.justinmangue.com

Leave a Reply

Your email address will not be published. Required fields are marked *