Created
January 10, 2010 12:37
-
-
Save bebraw/273475 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import re | |
class Assignment: | |
def matches(self, expression): | |
''' | |
>>> assignment = Assignment() | |
>>> assignment.matches('a=5') | |
True | |
>>> assignment.matches(' a = 5 ') | |
True | |
>>> assignment.matches('5=5') | |
False | |
Extra whitespace in name | |
>>> assignment.matches('bar bar = 5') | |
False | |
Assignment of a variable's value | |
>>> assignment.matches('a=b') | |
True | |
Underscore is allowed in the beginning | |
>>> assignment.matches('_fooDOO=bar') | |
True | |
>>> assignment.matches('a=b+5') | |
True | |
Multiple assignment | |
>>> assignment.matches('a=b=5') | |
True | |
Multipart | |
>>> assignment.matches('a, b = 5, 10') | |
True | |
>>> assignment.matches('a, b, c = 5, 10') | |
False | |
Multiple assignment and multipart | |
>>> assignment.matches('a, b = d, e = 10, 13') | |
True | |
Disallow minus in front of a name because "foo - -FOO" is | |
ambiguous | |
>>> assignment.matches('-FOO=b') | |
False | |
>>> assignment.matches('a, -b = 5, 10') | |
False | |
Disallow minus in middle of a name because "foo-doo" is | |
ambiguous | |
>>> assignment.matches('foo-doo=b') | |
False | |
Don't match if missing = | |
>>> assignment.matches('a + b') | |
False | |
Don't match empty | |
>>> assignment.matches('') | |
False | |
- expansion | |
>>> assignment.matches('FOO-=b') | |
True | |
+ expansion | |
>>> assignment.matches('FOO+=b') | |
True | |
* expansion | |
>>> assignment.matches('FOO*=b') | |
True | |
/ expansion | |
>>> assignment.matches('FOO/=b') | |
True | |
% expansion | |
>>> assignment.matches('FOO%=b') | |
True | |
''' | |
parts = self._split_expression(expression) | |
if len(parts) < 2: | |
return False | |
first_segments_len = len(parts[0].segments) | |
for part in parts: | |
if len(part.segments) != first_segments_len: | |
return False | |
if len(parts) == 2 and len(parts[0].segments) == 1: | |
l_part = parts[0].segments[0] | |
if not l_part.is_valid(): | |
return False | |
else: | |
for part in parts[:-1]: | |
for segment in part.segments: | |
if not segment.is_valid(): | |
return False | |
return True | |
def execute(self, expression, commands, variables): | |
''' | |
>>> from mock import Mock | |
>>> from py.test import raises | |
>>> python = Mock() | |
>>> commands = Mock() | |
>>> commands.find.return_value = python | |
>>> assignment = Assignment() | |
Simple assignment | |
>>> python.execute.return_value = 5 | |
>>> variables = {} | |
>>> assignment.execute('a=5', commands, variables) | |
>>> variables | |
{'a': 5} | |
Multipart | |
>>> python.execute.return_value = 5 | |
>>> variables = {} | |
>>> assignment.execute('a=b=5', commands, variables) | |
>>> variables | |
{'a': 5, 'b': 5} | |
Multiple assignment | |
TODO: check type conversion (int to str?) | |
>>> def execute(expression, variables): | |
... return expression | |
>>> python.execute = execute | |
>>> variables = {} | |
>>> assignment.execute('a, b = 5, 10', commands, variables) | |
>>> variables | |
{'a': '5', 'b': '10'} | |
Multiple assignment and multipart | |
TODO: check type conversion (int to str?) | |
>>> def execute(expression, variables): | |
... return expression | |
>>> python.execute = execute | |
>>> variables = {'a': 5, 'b': 6} | |
>>> assignment.execute('a, b = c, d = 5, 10', commands, variables) | |
>>> variables | |
{'a': '5', 'c': '5', 'b': '10', 'd': '10'} | |
Assignment of the value of another variable | |
>>> python.execute = Mock() | |
>>> python.execute.return_value = 10 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('b=a', commands, variables) | |
>>> variables | |
{'a': 10, 'b': 10} | |
Assignment of a result | |
>>> python.execute.return_value = 15 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('b=a+5', commands, variables) | |
>>> variables | |
{'a': 10, 'b': 15} | |
- expansion | |
>>> python.execute.return_value = 5 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('a-=5', commands, variables) | |
>>> variables | |
{'a': 5} | |
+ expansion | |
>>> python.execute.return_value = 15 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('a+=5', commands, variables) | |
>>> variables | |
{'a': 15} | |
* expansion | |
>>> python.execute.return_value = 50 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('a*=5', commands, variables) | |
>>> variables | |
{'a': 50} | |
/ expansion | |
>>> python.execute.return_value = 2 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('a/=5', commands, variables) | |
>>> variables | |
{'a': 2} | |
% expansion | |
>>> python.execute.return_value = 0 | |
>>> variables = {'a': 10} | |
>>> assignment.execute('a%=5', commands, variables) | |
>>> variables | |
{'a': 0} | |
Assignment of an invalid variable | |
>>> python.execute = Mock() | |
>>> python.execute.side_effect = NameError("name 'b' is not defined") | |
>>> variables = {} | |
>>> exception = raises(NameError, assignment.execute, 'a=b', commands, | |
... variables) | |
>>> exception.typename | |
'NameError' | |
>>> variables | |
{} | |
Assignment of an invalid variable - Multipart case | |
>>> python.execute = Mock() | |
>>> python.execute.side_effect = NameError("name 'c' is not defined") | |
>>> variables = {} | |
>>> exception = raises(NameError, assignment.execute, 'a=b=c', | |
... commands, variables) | |
>>> exception.typename | |
'NameError' | |
>>> variables | |
{} | |
Assignment of an invalid variable - Multiple assignment case | |
>>> python.execute = Mock() | |
>>> python.execute.side_effect = NameError("name 'c' is not defined") | |
>>> variables = {} | |
>>> exception = raises(NameError, assignment.execute, 'a,b=c,d', | |
... commands, variables) | |
>>> exception.typename | |
'NameError' | |
>>> variables | |
{} | |
''' | |
def set_variables(l_part, r_part): | |
python = commands.find('python') | |
for l_segment, r_segment in zip(l_part.segments, r_part.segments): | |
if r_segment in variables: | |
variables[l_segment] = variables[r_segment] | |
else: | |
variables[l_segment] = python.execute(r_segment, variables) | |
parts = self._split_expression(expression) | |
if len(parts) == 2: | |
if len(parts[1].segments) == 1: | |
parts[1].expand(parts[0]) | |
ret = set_variables(parts[0], parts[1]) | |
return ret | |
else: | |
for l_part in parts[:-1]: | |
ret = set_variables(l_part, parts[-1]) | |
if ret: | |
return ret | |
def _split_expression(self, expression): | |
class Segment(str): | |
def is_valid(self): | |
return re.match('^[a-zA-Z_]{1}[a-zA-Z_]*$', self) | |
class Part: | |
def __init__(self, value): | |
value = value.strip() | |
if len(value) > 0 and value[-1] in ('+', '-', '*', '/', '%'): | |
self.expansion = value[-1] | |
self.segments = (Segment(value[:-1]), ) | |
else: | |
self.expansion = None | |
self.segments = (Segment(value), ) | |
def expand(self, l_part): | |
expansion = l_part.expansion | |
if expansion: | |
self.segments = (l_part.segments[0] + expansion + \ | |
self.segments[0], ) | |
class SegmentedPart: | |
def __init__(self, raw_part): | |
segments = raw_part.split(',') | |
self.segments = [] | |
for segment in segments: | |
self.segments.append(Segment(segment.strip())) | |
expression = expression.strip() | |
parts = expression.split('=') | |
ret = [] | |
for part in parts: | |
if part.count(',') > 0: | |
ret.append(SegmentedPart(part)) | |
else: | |
ret.append(Part(part)) | |
return ret | |
if __name__ == "__main__": | |
import doctest | |
doctest.testmod() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment