diff options
author | Eevee <eevee.github@veekun.com> | 2014-12-09 14:34:18 -0800 |
---|---|---|
committer | Eevee <eevee.github@veekun.com> | 2014-12-09 14:34:18 -0800 |
commit | 604ad49bc14de31a4e39c64c58b1cb04bf43f8ee (patch) | |
tree | 3ddae1f6fb28f0cedb88d2c474dfe291891e12c1 | |
parent | fee56ccb6ec85b611b4fa392ba5b9dd3053423f6 (diff) | |
parent | d804c22987c20f1f607bda933a2b906f21dd315b (diff) | |
download | pyscss-604ad49bc14de31a4e39c64c58b1cb04bf43f8ee.tar.gz |
Merge pull request #314 from xen0n/modulo-expression
Support for modulo expressions
-rw-r--r-- | scss/grammar/expression.g | 2 | ||||
-rw-r--r-- | scss/grammar/expression.py | 19 | ||||
-rw-r--r-- | scss/tests/test_expression.py | 5 | ||||
-rw-r--r-- | scss/types.py | 18 |
4 files changed, 37 insertions, 7 deletions
diff --git a/scss/grammar/expression.g b/scss/grammar/expression.g index 454e112..8f4e5ad 100644 --- a/scss/grammar/expression.g +++ b/scss/grammar/expression.g @@ -61,6 +61,7 @@ parser SassExpression: token END: "$" token MUL: "[*]" token DIV: "/" + token MOD: "(?<=\s)%" token ADD: "[+]" token SUB: "-\s" token SIGN: "-(?![a-zA-Z_])" @@ -218,6 +219,7 @@ parser SassExpression: ( MUL u_expr {{ v = BinaryOp(operator.mul, v, u_expr) }} | DIV u_expr {{ v = BinaryOp(operator.truediv, v, u_expr) }} + | MOD u_expr {{ v = BinaryOp(operator.mod, v, u_expr) }} )* {{ return v }} rule u_expr: diff --git a/scss/grammar/expression.py b/scss/grammar/expression.py index b2ba877..a808eae 100644 --- a/scss/grammar/expression.py +++ b/scss/grammar/expression.py @@ -58,6 +58,7 @@ class SassExpressionScanner(Scanner): ('END', '$'), ('MUL', '[*]'), ('DIV', '/'), + ('MOD', '(?<=\\s)%'), ('ADD', '[+]'), ('SUB', '-\\s'), ('SIGN', '-(?![a-zA-Z_])'), @@ -291,10 +292,14 @@ class SassExpression(Parser): MUL = self._scan('MUL') u_expr = self.u_expr() v = BinaryOp(operator.mul, v, u_expr) - else: # == 'DIV' + elif _token_ == 'DIV': DIV = self._scan('DIV') u_expr = self.u_expr() v = BinaryOp(operator.truediv, v, u_expr) + else: # == 'MOD' + MOD = self._scan('MOD') + u_expr = self.u_expr() + v = BinaryOp(operator.mod, v, u_expr) return v def u_expr(self): @@ -517,7 +522,7 @@ class SassExpression(Parser): atom_chks_ = frozenset(['BAREWORD', 'INTERP_START']) expr_map_or_list_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'RPAR', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE', '","']) u_expr_chks = frozenset(['LPAR', 'DOUBLE_QUOTE', 'BAREWORD', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'ALPHA_FUNCTION', 'VAR', 'NUM', 'FNCT', 'LITERAL_FUNCTION', 'BANG_IMPORTANT', 'SINGLE_QUOTE']) - m_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) + m_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","']) interpolated_bare_url_rsts_ = frozenset(['RPAR', 'INTERP_START', 'BAREURL', 'SPACE']) argspec_items_rsts = frozenset(['RPAR', 'END', '","']) expr_slst_chks = frozenset(['INTERP_END', 'RPAR', 'END', '":"', '","']) @@ -527,12 +532,12 @@ class SassExpression(Parser): a_expr_chks = frozenset(['ADD', 'SUB']) interpolated_function_parens_rsts = frozenset(['LPAR', 'RPAR', 'INTERP_START']) expr_slst_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'VAR', 'END', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'FNCT', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'RPAR', '":"', 'NOT', 'INTERP_END', 'BANG_IMPORTANT', 'SINGLE_QUOTE', '","']) - interpolated_bareword_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SPACE', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) - atom_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'VAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'UNITS', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) + interpolated_bareword_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SPACE', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","']) + atom_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'VAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'UNITS', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","']) or_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'OR', 'NOT', 'SINGLE_QUOTE', '","']) argspec_chks_ = frozenset(['END', 'RPAR']) interpolated_string_single_rsts = frozenset(['SINGLE_QUOTE', 'INTERP_START']) - interpolated_bareword_rsts_ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'BAREWORD', 'GT', 'END', 'SPACE', 'SIGN', 'LITERAL_FUNCTION', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) + interpolated_bareword_rsts_ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'INTERP_END', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'BAREWORD', 'GT', 'END', 'SPACE', 'SIGN', 'LITERAL_FUNCTION', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","']) and_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NUM', '":"', 'BAREWORD', 'END', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'FNCT', 'VAR', 'AND', 'OR', 'NOT', 'SINGLE_QUOTE', '","']) comparison_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'ADD', 'FNCT', 'VAR', 'EQ', 'AND', 'GE', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) argspec_chks = frozenset(['DOTDOTDOT', 'SLURPYVAR']) @@ -548,8 +553,8 @@ class SassExpression(Parser): argspec_items_rsts_ = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'END', 'SLURPYVAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'DOTDOTDOT', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'RPAR', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE']) a_expr_rsts = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'INTERP_END', 'BANG_IMPORTANT', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) interpolated_string_rsts = frozenset(['DOUBLE_QUOTE', 'SINGLE_QUOTE']) - interpolated_bareword_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'OR', '","']) - m_expr_chks = frozenset(['MUL', 'DIV']) + interpolated_bareword_rsts__ = frozenset(['LPAR', 'DOUBLE_QUOTE', 'SUB', 'ALPHA_FUNCTION', 'RPAR', 'MUL', 'INTERP_END', 'BANG_IMPORTANT', 'DIV', 'LE', 'URL_FUNCTION', 'INTERP_START', 'COLOR', 'NE', 'LT', 'NUM', '":"', 'LITERAL_FUNCTION', 'GT', 'END', 'SIGN', 'BAREWORD', 'GE', 'FNCT', 'VAR', 'EQ', 'AND', 'ADD', 'SINGLE_QUOTE', 'NOT', 'MOD', 'OR', '","']) + m_expr_chks = frozenset(['MUL', 'DIV', 'MOD']) goal_interpolated_anything_rsts = frozenset(['END', 'INTERP_START']) interpolated_bare_url_rsts = frozenset(['RPAR', 'INTERP_START']) argspec_items_chks = frozenset(['KWVAR', 'LPAR', 'DOUBLE_QUOTE', 'VAR', 'URL_FUNCTION', 'BAREWORD', 'COLOR', 'ALPHA_FUNCTION', 'INTERP_START', 'SIGN', 'LITERAL_FUNCTION', 'ADD', 'NUM', 'FNCT', 'NOT', 'BANG_IMPORTANT', 'SINGLE_QUOTE']) diff --git a/scss/tests/test_expression.py b/scss/tests/test_expression.py index d895948..33adfe2 100644 --- a/scss/tests/test_expression.py +++ b/scss/tests/test_expression.py @@ -55,6 +55,11 @@ def test_reference_operations(): assert calc('#{$font-size}/#{$line-height}') == String('12px / 30px') # uses #{}; does no division + # Modulo + assert calc('29 % 12') == Number(5) + assert calc('29px % 12') == Number(5, 'px') + assert calc('29px % 12px') == Number(5, 'px') + # Color operations ns.set_variable('$translucent-red', Color.from_rgb(1, 0, 0, 0.5)) ns.set_variable('$green', Color.from_name('lime')) diff --git a/scss/types.py b/scss/types.py index 092f93b..54345da 100644 --- a/scss/types.py +++ b/scss/types.py @@ -396,6 +396,24 @@ class Number(Value): return Number(amount, unit_numer=numer, unit_denom=denom) + def __mod__(self, other): + if not isinstance(other, Number): + return NotImplemented + + amount = self.value % other.value + + if self.is_unitless: + return Number(amount) + + if not other.is_unitless: + left = self.to_base_units() + right = other.to_base_units() + + if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom: + raise ValueError("Can't reconcile units: %r and %r" % (self, other)) + + return Number(amount, unit_numer=self.unit_numer, unit_denom=self.unit_denom) + def __add__(self, other): # Numbers auto-cast to strings when added to other strings if isinstance(other, String): |