Skip to content

Commit

Permalink
fixing binary operations support
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredly committed Jun 24, 2010
1 parent 74de1af commit fae7dc2
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 28 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
@@ -1,4 +1,4 @@
include README.rst
include setup.py
recursive-include codetalker *.py
recursive-include clevercss *.py
recursive-include tests *.py
17 changes: 14 additions & 3 deletions clevercss/grammar.py
Expand Up @@ -70,12 +70,26 @@ def atomic(rule):
rule | (literal, star(_or(post_attr, post_subs, post_call)))
rule.astAttrs = {'literal':'literal', 'posts':'post_attr, post_subs, post_call'}

def mul_ex(rule):
rule | (atomic, star(_or(*'*/'), atomic))
rule.astAttrs = {'left': 'atomic', 'ops': 'SYMBOL[]', 'values': 'atomic[1:]'}
mul_ex.astName = 'BinOp'

def add_ex(rule):
rule | (mul_ex, star(_or(*'-+'), mul_ex))
rule.astAttrs = {'left': 'BinOp', 'ops': 'SYMBOL[]', 'values': 'BinOp[1:]'}
add_ex.astName = 'BinOp'

# add_ex = binop('add_ex', '+-', mul_ex)
# add_ex.astHelp = 'value (or expression)'

def literal(rule):
rule | paren | STRING | CSSID | CSSNUMBER | CSSCOLOR
rule.astAll = True

def paren(rule):
rule | ('(', add_ex, ')')
rule.astAttrs = {'value': 'BinOp'}

def post_attr(rule):
rule | ('.', CSSID)
Expand All @@ -96,9 +110,6 @@ def post(rule):
def commas(item):
return (item, star(',', item), [','])

mul_ex = binop('mul_ex', '*/', atomic)
add_ex = binop('add_ex', '+-', mul_ex)
add_ex.astHelp = 'value (or expression)'

grammar = Grammar(start=start, indent=True, tokens=[CSSSELECTOR, STRING, CSSID, CSSNUMBER, CSSCOLOR, CCOMMENT, SYMBOL, NEWLINE, WHITE], ignore=[WHITE, CCOMMENT])

Expand Down
11 changes: 10 additions & 1 deletion clevercss/translator.py
Expand Up @@ -143,7 +143,12 @@ def BinOp(node, scope):
result = translate(node.left, scope)
operators = {'*': operator.mul, '/': operator.div, '+': operator.add, '-': operator.sub}
for op, value in zip(node.ops, node.values):
result = operators[op](result, translate(value, scope))
try:
nv = translate(value, scope)
result = operators[op.value](result, nv)
except TypeError:
print [result, nv]
raise
return result

@translates(grammar.CSSCOLOR)
Expand All @@ -154,4 +159,8 @@ def color(node, scope):
def number(node, scope):
return values.Number(node.value)

@translates('paren')
def paren(node, scope):
return translate(node.value, scope)

# vim: et sw=4 sts=4
26 changes: 21 additions & 5 deletions clevercss/values.py
@@ -1,4 +1,5 @@
#!/usr/bin/env python
import operator
import consts
import re

Expand All @@ -15,6 +16,14 @@ def parse(self, value):
def __repr__(self):
return str(self)

def calc(self, op, other):
return NotImplemented

__add__ = lambda self, other: self.calc(other, operator.add)
__sub__ = lambda self, other: self.calc(other, operator.sub)
__div__ = lambda self, other: self.calc(other, operator.div)
__mul__ = lambda self, other: self.calc(other, operator.mul)

class Number(Value):
rx = re.compile(r'(-?(?:\d+(?:\.\d+)?|\.\d+))(px|em|%|pt)?')
def parse(self, value):
Expand All @@ -32,6 +41,18 @@ def __str__(self):
return u'%s%s' % self.value
return str(self.value[0])

def calc(self, other, op):
if isinstance(other, Number):
if other.value[1] == self.value[1]:
return Number((op(self.value[0], other.value[0]), self.value[1]), False)
elif self.value[1] and other.value[1]:
raise ValueError('cannot do math on numbers of differing units')
elif self.value[1]:
return Number((op(self.value[0], other.value[0]), self.value[1]), False)
elif other.value[1]:
return Number((op(self.value[0], other.value[0]), other.value[1]), False)
return NotImplemented

methods = ['abs', 'round']
def abs(self):
return Number((abs(self.value[0]), self.value[1]), False)
Expand Down Expand Up @@ -62,11 +83,6 @@ def calc(self, other, op):
return Color(tuple(op(a, other.value[0]) for a in self.value), False)
return NotImplemented

__add__ = lambda self, other: self.calc(other, operator.add)
__sub__ = lambda self, other: self.calc(other, operator.sub)
__div__ = lambda self, other: self.calc(other, operator.div)
__mul__ = lambda self, other: self.calc(other, operator.mul)

methods = ['brighten', 'darken']

def brighten(self, amount=Number('10%')):
Expand Down
58 changes: 40 additions & 18 deletions tests/parsing.py
Expand Up @@ -3,11 +3,12 @@
import magictest
from magictest import MagicTest as TestCase

import clevercss
from clevercss.grammar import grammar
from codetalker.pgm.grammar import Text, ParseError
from codetalker.pgm.errors import *

class CCSSTest(TestCase):
class Tokenize(TestCase):
def tokens(self):
text = Text('hello = world')
tokens = grammar.get_tokens(text)
Expand All @@ -21,6 +22,8 @@ def token_lines(self):
self.assertEqual(len(tokens.tokens), 8)
self.assertEqual(tokens.tokens[-2].lineno, 2)


class Parse(TestCase):
def parse(self):
text = 'hello=orld\n\nother=bar\n'
tree = check_parse(text)
Expand Down Expand Up @@ -58,21 +61,8 @@ def neg_number(self):
check_parse('one = -10em')
check_parse('one = -10%')

def check_parse(text):
try:
return grammar.process(text)
except:
return grammar.process(text, debug=True)

def check_fail(text):
try:
grammar.process(text)
except:
pass
else:
grammar.process(text, debug=True)
raise Exception('was supposed to fail on \'%s\'' % text.encode('string_escape'))

def url(self):
check_parse('one = url("hello.png")')

def make_pass(grammar, text):
def meta(self):
Expand Down Expand Up @@ -108,9 +98,41 @@ def meta(self):
),()]

for i, good in enumerate(strings[0]):
setattr(CCSSTest, '%d_good' % i, make_pass(grammar, good))
setattr(Parse, '%d_good' % i, make_pass(grammar, good))
for i, bad in enumerate(strings[1]):
setattr(CCSSTest, '%d_bad' % i, make_fail(grammar, good))
setattr(Parse, '%d_bad' % i, make_fail(grammar, good))

class Convert(TestCase):
pass

cases = (('body:\n top: 5', 'body {\n top: 5;\n}\n', 'basic'),
('body:\n top: 2+4', 'body {\n top: 6;\n}\n', 'add'),
('body:\n top: (5+4 - 1) /2', 'body {\n top: 4;\n}\n', 'math'),
('one = 2\nbody:\n top: one', 'body {\n top: 2;\n}\n', 'vbl'),
('one = 2\nbody:\n top: one+3', 'body {\n top: 5;\n}\n', 'vbl2'))

def make_convert(ccss, css):
def meta(self):
self.assertEqual(clevercss.convert(ccss), css)
return meta

for i, (ccss, css, name) in enumerate(cases):
setattr(Convert, 'convert_%d_%s' % (i, name), make_convert(ccss, css))

def check_parse(text):
try:
return grammar.process(text)
except:
return grammar.process(text, debug=True)

def check_fail(text):
try:
grammar.process(text)
except:
pass
else:
grammar.process(text, debug=True)
raise Exception('was supposed to fail on \'%s\'' % text.encode('string_escape'))

all_tests = magictest.suite(__name__)

Expand Down

0 comments on commit fae7dc2

Please sign in to comment.