Hi all,<br><br><b>About</b><br>This email is asking for feedback on different alternatives of implementing a feedback request. It also goes through a lot of thought processes that have resulted in my preferred suggestion.<br>
<br>This is mainly addressed at Sugar devs, but thought Alastair & Grant
from Wellington this morning might be interested.<br><br><br><b>Background => User story</b><br>This morning while we were testing with some parents, the question came up whether Speak could respond to maths problems, e.g. if you entered 10+2, would speak respond with 12?<br>
<br><br><b>First thoughts</b><br>I thought this was quite a neat suggestion and have encountered a few ways to implement it. To start with, I thought a smart way to indicate that you wanted Speak to tell you an answer would be to use the equals sign after maths. That leads to a construction like this:<br>
<br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">if text.rstrip()[-1:] == '=':</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> try:</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> text = str(eval(text.rstrip()[:-1]))</span><span style="font-family: courier new,monospace;"></span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> except:<br>
</span><span style="font-family: courier new,monospace;"> # SyntaxError is
called when eval can't handle the input, </span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> # but I think we
should obscure all errors</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> pass<br><font face="arial,helvetica,sans-serif"><br></font></span>We need to convert the result of the eval to a string because speaking
seems to get confused when it encounters an integer. <span style="font-family: courier new,monospace;"><font face="arial,helvetica,sans-serif">The practical result of the code above is to produce results similar to this below: </font><br>
<br>>>> text = '3+4= '<br>>>> str(eval(text.rstrip()[:-1]))<br>'7'<br></span><br>One problem with this approach is integer division. Speak I am not quite sure yet how to simply convert all integers within <span style="font-family: courier new,monospace;">text</span> to floats and keep things succint & snappy. I thought it would be better to bring something back to the list and have a chat about trade offs.<br>
<br>Also, what do people think about the risks of exposing <span style="font-family: courier new,monospace;">eval()</span> to users? My thoughts are that as we're forcing an '=' to be at the end of the string, it will always result in SyntaxErrors. if something dangerous is attempted to be evaluated.<br>
<br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">>>> eval('str')</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"><type 'str'></span><br>
<br>vs.<br><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">>>> eval('str=')</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">Traceback (most recent call last):</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> File "<stdin>", line 1, in <module></span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> File "<string>", line 1</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> str=</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> ^</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;">SyntaxError: unexpected EOF while parsing</span><br><br><br>Even so, is there a simple way to expose mathematical operators but prevent calls to functions & methods?<br>
<span style="font-family: courier new,monospace;"><font face="arial,helvetica,sans-serif"><br></font></span><b>Where to implement?</b><br><br>The easiest place for the user would be to be agnostic of mode [1]. E.g. whenever an equals sign appears at the end, try to answer the maths and say the result. <br>
<br>it is very easy to tel someone 'add an equals sign at the end'. However, since we have a Robot mode, it's probably best to throw it in there [2]. However, it's probably best for code maintenance to put the evaluation in the 'brain.py', rather than in the voice, so that's where solution [3] puts it.<br>
<br>[1] activity.py trunk:377<br><br>Will evaluate maths problems agnostic of mode:<br><br><span style="font-family: courier new,monospace;">def _entry_activate_cb(self, entry):<br> # the user pressed Return, say the text and clear it out<br>
text = entry.props.text.rstrip()<br> maths_problem = False<br> if text:<br> self.face.look_ahead()<br><br> # solve the maths problem<br> if text[-1:] == '=':<br> try:<br> text = str(eval(text[:-1]))<br>
maths_problem = True<br> except:<br> pass<br><br> # speak the text<br> if self._mode == MODE_BOT and not maths_problem:<br> self.face.say(<br> brain.respond(self.voices.props.value, text))<br>
else:<br> self.face.say(text)</span><br><br>[2] activity.py trunk:377<br><br>Will evaluate maths only if we're in robot mode<br><br><span style="font-family: courier new,monospace;">def _entry_activate_cb(self, entry):<br>
# the user pressed Return, say the text and clear it out<br> text = entry.props.text.rstrip()<br> if text:<br> self.face.look_ahead()<br><br> # speak the text<br> if self._mode == MODE_BOT:<br>
if text[-1:] == '=':<br> try:<br> text = str(eval(text[:-1]))<br> except:<br> self.face.say(text)<br> self.face.say(<br> brain.respond(self.voices.props.value, text))<br>
else:<br> self.face.say(text)</span><br><br><br>[3] brain.py trunk:66:<br><br>Moving the logic from [2], to the "brain" of the program.<br><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;">def respond(voice, text):</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> if text.rstrip()[-1:] == '=':</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> try:</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> text = str(eval(text.rstrip()[:-1]))</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> except:</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> pass</span><br style="font-family: courier new,monospace;"><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> if _kernel is None:</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> return text</span><br style="font-family: courier new,monospace;"><span style="font-family: courier new,monospace;"> else:</span><br style="font-family: courier new,monospace;">
<span style="font-family: courier new,monospace;"> return _kernel.respond(text)</span><br><br><b>Summary</b><br><br>I personally prefer [1] because it means that you don't need to change modes. However, that does mean we're overloading the default speech mode. If there's no support, then [3] is probably be the best way to keep logic in the right place. <br>
<br>Thanks <br>