Converting tiny subset of English to Python - any suggestions how?

Started by
7 comments, last by crivens 15 years, 10 months ago
I'm trying to write a 'wizard' for users to make it easier for them generate Python logic, which in this app is basically a bunch of if statements to implement business logic. The users aren't developers and asking them to write Python won't go down well. One option that sprung to mind was to let them use a tiny subset of English to generate the business logic, and then write a tool to parse and convert it to Python. The business logic is contains filters (if statements) and modifiers (modifying data, obviously!). Here's a simple example: The English for the filter would be:
person.name is "fred" and person.age is 25 and person.address contains "canada"
The tool would convert this to Python:
if person.get_value("name") == "fred" and person.get_value("age") == 25 and person.get_status().find("canada") > -1:
The English for the modifier would be:
person.salary to 20000 and person.status to "hired"
Which would be converted to:

person.set_value("salary", 20000)
person.set_value("status", "hired")

Hopefully that makes sense. I started writing my tool to perform the filter conversion. I parse the string andI break it down into tokens for each possible English word, so: TOKEN_AND TOKEN_STRING TOKEN_VARIABLE (e.g. person) etc. I've also added logic to determine which tokens can precede or follow other tokens and that works nicely. But what's next? How do I take the list (stream I guess) of tokens and convert it into Python? Do I turn the stream into a tree, where, for example, TOKEN_AND is a branch, and TOKEN_STRING is a leaf? Then traverse the tree to generate the Python? I tried this, but I couldn't get it working. Any suggestions? Many thanks
Advertisement
get_value? set_value?

If you'd just write idiomatic Python, your users could write statements directly in Python rather than an arbitrary "English subset". You can still validate on write or compute on read by using Python properties.

class Person(object):    def __init__(self, name, age, address):        self.__name = name        self.__age = age        self.__address = address.split('\n') # break on newlines, creating a list        # set other default properties here    def __set_name(self, value):        # perform validation of value here                self.__name = value    name = property((lambda self: self.__name), __set_name)    # other setters and property declarations here


Now your users can write:
person.name is 'fred' and person.age is 25 and 'canada' in person.address

...and it's actually Python code!
The Person object is a C++ class wrapped using Boost Python, and data is accessed using set_value/get_value. Odd I know, but it's legacy and zero chance to change it. :)

Thanks - I'll try your suggestion out.
The Python interface of an exposed class does not have to match the C++ interface.
Quote:Original post by SiCrane
The Python interface of an exposed class does not have to match the C++ interface.


Yeah that took me a while to realise, for some reason, and now I'm considering redefining __getattr__ and __setattr__.
Quote:Original post by Oluseyi
get_value? set_value?

If you'd just write idiomatic Python, your users could write statements directly in Python rather than an arbitrary "English subset". You can still validate on write or compute on read by using Python properties.

*** Source Snippet Removed ***

Now your users can write:
person.name is 'fred' and person.age is 25 and 'canada' in person.address

...and it's actually Python code!


I think this will work beautifully. I added functions for__getattr__ and __setattr__ to my base class (that Person derives from) that simply do this. I can't see anything that might be dangerous here.

CString __getattr__ (CEntity& entity, const CString& value){   return entity.GetValue(value);}


Sweet!! My boss seems impressed and I think it might just help appease the non-technical users.
That's weird. This never evaluates:

person.name is "fred"


But this does:

person.name == "fred"


It'll only correctly determine string equality when I use the equals operator (==). If I use 'is', the equality always fails. Any ideas?

It only fails when executing python script embedded in my app. It works fine if I run the same source from an IDE (PyScipter).
Quote:Original post by crivens
Any ideas?

The is operator evaluates reference equality. The == operator evaluates value equality. Since Python strings are immutable, multiple strings with the same literal value will all refer to the same object, and thus will yield both value and reference equality (even for computed values: 'fred' is 'fr' + 'ed' returns True).

When your python is embedded in your app, however, I suspect that the values in person are being set by the C++ side of things, which means it is not the same object (reference inequality) as the Python side, this is fails but == succeeds.

The only solution I can think of is to instrument your __getattr__ method to return a Python-equivalent string object when queried for name, so is will work, or to define an automatic conversion between CString and PyStringObject.
Thanks! I read up on 'is' and came to the same conclusion. I change my __getattr__ function (this is C++) to return boost::python::str but it doesn't seem to correct the issue.

boost::python::str get_attr(CEntity& entity, const CString& name){	return boost::python::str(entity.GetValue(name));}


And then later:

		.def("__getattr__", &get_attr)

This topic is closed to new replies.

Advertisement