diff options
author | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-08-15 15:45:36 -0700 |
---|---|---|
committer | Eevee (Alex Munroe) <eevee.git@veekun.com> | 2013-08-15 15:45:36 -0700 |
commit | 0f12919835eabdfef8a7b3b981de057bb8bc5957 (patch) | |
tree | 1ee713ade7a015a0a684a81b14f17d9521c4de44 | |
parent | 85cae01f116d0177c8f74d17ac1945b845b902c0 (diff) | |
download | pyscss-0f12919835eabdfef8a7b3b981de057bb8bc5957.tar.gz |
Experimental first crack at maps support.
-rw-r--r-- | scss/expression.py | 89 | ||||
-rw-r--r-- | scss/functions/core.py | 44 | ||||
-rw-r--r-- | scss/src/grammar/grammar.g | 25 | ||||
-rw-r--r-- | scss/src/grammar/grammar.py | 70 | ||||
-rw-r--r-- | scss/tests/files/general/007-maps-example.css | 3 | ||||
-rw-r--r-- | scss/tests/files/general/007-maps-example.scss | 24 | ||||
-rw-r--r-- | scss/tests/functions/test_core.py | 8 | ||||
-rw-r--r-- | scss/types.py | 32 |
8 files changed, 248 insertions, 47 deletions
diff --git a/scss/expression.py b/scss/expression.py index 09d470d..802d35a 100644 --- a/scss/expression.py +++ b/scss/expression.py @@ -10,7 +10,7 @@ import six import scss.config as config from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re, _variable_re from scss.rule import Namespace -from scss.types import BooleanValue, ColorValue, ListValue, Null, NumberValue, ParserValue, String, Undefined +from scss.types import BooleanValue, ColorValue, ListValue, Map, Null, NumberValue, ParserValue, String, Undefined from scss.util import dequote, normalize_var ################################################################################ @@ -335,6 +335,23 @@ class ListLiteral(Expression): return ListValue(items, separator="," if self.comma else "") +class MapLiteral(Expression): + def __init__(self, pairs): + self.pairs = pairs + + def evaluate(self, calculator, divide=False): + # TODO unclear here whether the keys should be bare tokens or Literals; + # depends how the syntax works! + scss_pairs = [] + for key, value in self.pairs: + scss_pairs.append(( + String(key, quotes=None), + value.evaluate(calculator), + )) + + return Map(scss_pairs) + + class ArgspecLiteral(Expression): """Contains pairs of argument names and values, as parsed from a function definition or function call. @@ -424,8 +441,8 @@ class SassExpressionScanner(Scanner): patterns = None _patterns = [ ('":"', ':'), + ('","', ','), ('[ \r\t\n]+', '[ \r\t\n]+'), - ('COMMA', ','), ('LPAR', '\\(|\\['), ('RPAR', '\\)|\\]'), ('END', '$'), @@ -449,8 +466,10 @@ class SassExpressionScanner(Scanner): ('UNITS', '(?<!\\s)(?:[a-zA-Z]+|%)(?![-\\w])'), ('NUM', '(?:\\d+(?:\\.\\d*)?|\\.\\d+)'), ('COLOR', '#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])'), + ('KWVAR', '\\$[-a-zA-Z0-9_]+(?=\\s*:)'), ('VAR', '\\$[-a-zA-Z0-9_]+'), ('FNCT', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\()'), + ('KWID', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\s*:)'), ('ID', '[-a-zA-Z_][-a-zA-Z0-9_]*'), ('BANG_IMPORTANT', '!important'), ] @@ -579,9 +598,15 @@ class SassExpression(Parser): _token_ = self._peek(self.u_expr_chks) if _token_ == 'LPAR': LPAR = self._scan('LPAR') - expr_lst = self.expr_lst() + _token_ = self._peek(self.atom_rsts) + if _token_ == 'KWID': + expr_map = self.expr_map() + v = expr_map + else: # in self.not_expr_rsts + expr_lst = self.expr_lst() + v = Parentheses(expr_lst) RPAR = self._scan('RPAR') - return Parentheses(expr_lst) + return v elif _token_ == 'ID': ID = self._scan('ID') return Literal(parse_bareword(ID)) @@ -592,14 +617,14 @@ class SassExpression(Parser): FNCT = self._scan('FNCT') v = ArgspecLiteral([]) LPAR = self._scan('LPAR') - if self._peek(self.atom_rsts) != 'RPAR': + if self._peek(self.atom_rsts_) != 'RPAR': argspec = self.argspec() v = argspec RPAR = self._scan('RPAR') return CallOp(FNCT, v) elif _token_ == 'NUM': NUM = self._scan('NUM') - if self._peek(self.atom_rsts_) == 'UNITS': + if self._peek(self.atom_rsts__) == 'UNITS': UNITS = self._scan('UNITS') return Literal(NumberValue(float(NUM), unit=UNITS.lower())) return Literal(NumberValue(float(NUM))) @@ -616,11 +641,31 @@ class SassExpression(Parser): VAR = self._scan('VAR') return Variable(VAR) + def expr_map(self): + map_items = self.map_items() + return MapLiteral(map_items) + + def map_items(self): + map_item = self.map_item() + pairs = [map_item] + if self._peek(self.map_items_rsts) == '","': + self._scan('","') + if self._peek(self.map_items_rsts_) == 'KWID': + map_items = self.map_items() + pairs.extend(map_items) + return pairs + + def map_item(self): + KWID = self._scan('KWID') + self._scan('":"') + expr_slst = self.expr_slst() + return (KWID, expr_slst) + def argspec(self): argspec_item = self.argspec_item() v = [argspec_item] - while self._peek(self.argspec_rsts) == 'COMMA': - COMMA = self._scan('COMMA') + while self._peek(self.map_items_rsts) == '","': + self._scan('","') argspec_item = self.argspec_item() v.append(argspec_item) return ArgspecLiteral(v) @@ -639,8 +684,8 @@ class SassExpression(Parser): def expr_lst(self): expr_slst = self.expr_slst() v = [expr_slst] - while self._peek(self.expr_lst_rsts) == 'COMMA': - COMMA = self._scan('COMMA') + while self._peek(self.expr_lst_rsts) == '","': + self._scan('","') expr_slst = self.expr_slst() v.append(expr_slst) return ListLiteral(v) if len(v) > 1 else v[0] @@ -654,23 +699,25 @@ class SassExpression(Parser): return ListLiteral(v, comma=False) if len(v) > 1 else v[0] m_expr_chks = set(['MUL', 'DIV']) - comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR']) - atom_rsts = set(['LPAR', 'BANG_IMPORTANT', 'COLOR', 'QSTR', 'SIGN', 'NOT', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'RPAR', 'ID']) + map_items_rsts_ = set(['KWID', 'RPAR']) + comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","']) + atom_rsts = set(['KWID', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + atom_rsts__ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) u_expr_chks = set(['LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR']) - argspec_item_rsts_ = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', 'FNCT']) - expr_lst_rsts = set(['END', 'COMMA', 'RPAR']) - argspec_rsts = set(['COMMA', 'RPAR']) - and_expr_rsts = set(['AND', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'COMMA', 'FNCT', 'STR', 'NOT', 'ID', 'RPAR', 'OR']) + m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + map_items_rsts = set(['RPAR', '","']) + expr_lst_rsts = set(['RPAR', 'END', '","']) + and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - expr_rsts = set(['LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'RPAR', 'VAR', 'ADD', 'NUM', 'COMMA', 'FNCT', 'STR', 'NOT', 'ID', 'SIGN', 'OR']) + expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) not_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) argspec_item_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'NOT', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - atom_rsts_ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR']) + atom_rsts_ = set(['LPAR', 'BANG_IMPORTANT', 'COLOR', 'QSTR', 'SIGN', 'NOT', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'RPAR', 'ID']) comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ']) a_expr_chks = set(['ADD', 'SUB']) - a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR']) - expr_slst_rsts = set(['LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'RPAR', 'VAR', 'ADD', 'NUM', 'COMMA', 'FNCT', 'STR', 'NOT', 'SIGN', 'ID']) + a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + argspec_item_rsts_ = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', 'FNCT']) + expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","']) ### Grammar ends. diff --git a/scss/functions/core.py b/scss/functions/core.py index 8e1c1ae..96c87a5 100644 --- a/scss/functions/core.py +++ b/scss/functions/core.py @@ -435,6 +435,7 @@ CORE_LIBRARY.add(NumberValue.wrap_python_function(math.floor), 'floor', 1) # ------------------------------------------------------------------------------ # List functions + def __parse_separator(separator, default_from=None): if separator is None: return None @@ -536,6 +537,49 @@ def index(lst, val): # ------------------------------------------------------------------------------ +# Map functions + +@register('map-get', 2) +def map_get(map, key): + print(repr(map.index.keys())) + return map.index[key] + + +@register('map-merge', 2) +def map_merge(*maps): + pairs = [] + index = {} + for map in maps: + for key, value in map.pairs: + if key in index: + continue + + pairs.append((key, value)) + index[key] = value + + return Map(pairs) + + +@register('map-keys', 1) +def map_keys(map): + return List( + [k for (k, v) in map.pairs], + comma=True) + + +@register('map-values', 1) +def map_values(map): + return List( + [v for (k, v) in map.pairs], + comma=True) + + +@register('map-has-key', 2) +def map_values(map, key): + return BooleanValue(key in map.index) + + +# ------------------------------------------------------------------------------ # Meta functions @register('type-of', 1) diff --git a/scss/src/grammar/grammar.g b/scss/src/grammar/grammar.g index 00c55f3..4eac9ef 100644 --- a/scss/src/grammar/grammar.g +++ b/scss/src/grammar/grammar.g @@ -5,7 +5,6 @@ %% parser SassExpression: ignore: "[ \r\t\n]+" - token COMMA: "," token LPAR: "\\(|\\[" token RPAR: "\\)|\\]" token END: "$" @@ -29,8 +28,10 @@ parser SassExpression: token UNITS: "(?<!\s)(?:[a-zA-Z]+|%)(?![-\w])" token NUM: "(?:\d+(?:\.\d*)?|\.\d+)" token COLOR: "#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])" + token KWVAR: "\$[-a-zA-Z0-9_]+(?=\s*:)" token VAR: "\$[-a-zA-Z0-9_]+" token FNCT: "[-a-zA-Z_][-a-zA-Z0-9_]*(?=\()" + token KWID: "[-a-zA-Z_][-a-zA-Z0-9_]*(?=\s*:)" token ID: "[-a-zA-Z_][-a-zA-Z0-9_]*" token BANG_IMPORTANT: "!important" @@ -76,7 +77,10 @@ parser SassExpression: | ADD u_expr {{ return UnaryOp(operator.pos, u_expr) }} | atom {{ return atom }} - rule atom: LPAR expr_lst RPAR {{ return Parentheses(expr_lst) }} + rule atom: LPAR ( + expr_map {{ v = expr_map }} + | expr_lst {{ v = Parentheses(expr_lst) }} + ) RPAR {{ return v }} | ID {{ return Literal(parse_bareword(ID)) }} | BANG_IMPORTANT {{ return Literal(String(BANG_IMPORTANT, quotes=None)) }} | FNCT {{ v = ArgspecLiteral([]) }} @@ -91,9 +95,22 @@ parser SassExpression: | COLOR {{ return Literal(ColorValue(ParserValue(COLOR))) }} | VAR {{ return Variable(VAR) }} + + rule expr_map: map_items {{ return MapLiteral(map_items) }} + + rule map_items: map_item {{ pairs = [map_item] }} + [ + "," + [ map_items {{ pairs.extend(map_items) }} + ] + ] {{ return pairs }} + + rule map_item: KWID ":" expr_slst {{ return (KWID, expr_slst) }} + + rule argspec: argspec_item {{ v = [argspec_item] }} ( - COMMA + "," argspec_item {{ v.append(argspec_item) }} )* {{ return ArgspecLiteral(v) }} @@ -107,7 +124,7 @@ parser SassExpression: rule expr_lst: expr_slst {{ v = [expr_slst] }} ( - COMMA + "," expr_slst {{ v.append(expr_slst) }} )* {{ return ListLiteral(v) if len(v) > 1 else v[0] }} diff --git a/scss/src/grammar/grammar.py b/scss/src/grammar/grammar.py index 4435465..d78b65f 100644 --- a/scss/src/grammar/grammar.py +++ b/scss/src/grammar/grammar.py @@ -12,8 +12,8 @@ class SassExpressionScanner(Scanner): patterns = None _patterns = [ ('":"', ':'), + ('","', ','), ('[ \r\t\n]+', '[ \r\t\n]+'), - ('COMMA', ','), ('LPAR', '\\(|\\['), ('RPAR', '\\)|\\]'), ('END', '$'), @@ -37,8 +37,10 @@ class SassExpressionScanner(Scanner): ('UNITS', '(?<!\\s)(?:[a-zA-Z]+|%)(?![-\\w])'), ('NUM', '(?:\\d+(?:\\.\\d*)?|\\.\\d+)'), ('COLOR', '#(?:[a-fA-F0-9]{6}|[a-fA-F0-9]{3})(?![a-fA-F0-9])'), + ('KWVAR', '\\$[-a-zA-Z0-9_]+(?=\\s*:)'), ('VAR', '\\$[-a-zA-Z0-9_]+'), ('FNCT', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\()'), + ('KWID', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\s*:)'), ('ID', '[-a-zA-Z_][-a-zA-Z0-9_]*'), ('BANG_IMPORTANT', '!important'), ] @@ -167,9 +169,15 @@ class SassExpression(Parser): _token_ = self._peek(self.u_expr_chks) if _token_ == 'LPAR': LPAR = self._scan('LPAR') - expr_lst = self.expr_lst() + _token_ = self._peek(self.atom_rsts) + if _token_ == 'KWID': + expr_map = self.expr_map() + v = expr_map + else: # in self.not_expr_rsts + expr_lst = self.expr_lst() + v = Parentheses(expr_lst) RPAR = self._scan('RPAR') - return Parentheses(expr_lst) + return v elif _token_ == 'ID': ID = self._scan('ID') return Literal(parse_bareword(ID)) @@ -180,14 +188,14 @@ class SassExpression(Parser): FNCT = self._scan('FNCT') v = ArgspecLiteral([]) LPAR = self._scan('LPAR') - if self._peek(self.atom_rsts) != 'RPAR': + if self._peek(self.atom_rsts_) != 'RPAR': argspec = self.argspec() v = argspec RPAR = self._scan('RPAR') return CallOp(FNCT, v) elif _token_ == 'NUM': NUM = self._scan('NUM') - if self._peek(self.atom_rsts_) == 'UNITS': + if self._peek(self.atom_rsts__) == 'UNITS': UNITS = self._scan('UNITS') return Literal(NumberValue(float(NUM), unit=UNITS.lower())) return Literal(NumberValue(float(NUM))) @@ -204,11 +212,31 @@ class SassExpression(Parser): VAR = self._scan('VAR') return Variable(VAR) + def expr_map(self): + map_items = self.map_items() + return MapLiteral(map_items) + + def map_items(self): + map_item = self.map_item() + pairs = [map_item] + if self._peek(self.map_items_rsts) == '","': + self._scan('","') + if self._peek(self.map_items_rsts_) == 'KWID': + map_items = self.map_items() + pairs.extend(map_items) + return pairs + + def map_item(self): + KWID = self._scan('KWID') + self._scan('":"') + expr_slst = self.expr_slst() + return (KWID, expr_slst) + def argspec(self): argspec_item = self.argspec_item() v = [argspec_item] - while self._peek(self.argspec_rsts) == 'COMMA': - COMMA = self._scan('COMMA') + while self._peek(self.map_items_rsts) == '","': + self._scan('","') argspec_item = self.argspec_item() v.append(argspec_item) return ArgspecLiteral(v) @@ -227,8 +255,8 @@ class SassExpression(Parser): def expr_lst(self): expr_slst = self.expr_slst() v = [expr_slst] - while self._peek(self.expr_lst_rsts) == 'COMMA': - COMMA = self._scan('COMMA') + while self._peek(self.expr_lst_rsts) == '","': + self._scan('","') expr_slst = self.expr_slst() v.append(expr_slst) return ListLiteral(v) if len(v) > 1 else v[0] @@ -242,23 +270,25 @@ class SassExpression(Parser): return ListLiteral(v, comma=False) if len(v) > 1 else v[0] m_expr_chks = set(['MUL', 'DIV']) - comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR']) - atom_rsts = set(['LPAR', 'BANG_IMPORTANT', 'COLOR', 'QSTR', 'SIGN', 'NOT', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'RPAR', 'ID']) + map_items_rsts_ = set(['KWID', 'RPAR']) + comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","']) + atom_rsts = set(['KWID', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + atom_rsts__ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) u_expr_chks = set(['LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR']) - argspec_item_rsts_ = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', 'FNCT']) - expr_lst_rsts = set(['END', 'COMMA', 'RPAR']) - argspec_rsts = set(['COMMA', 'RPAR']) - and_expr_rsts = set(['AND', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'COMMA', 'FNCT', 'STR', 'NOT', 'ID', 'RPAR', 'OR']) + m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + map_items_rsts = set(['RPAR', '","']) + expr_lst_rsts = set(['RPAR', 'END', '","']) + and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - expr_rsts = set(['LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'RPAR', 'VAR', 'ADD', 'NUM', 'COMMA', 'FNCT', 'STR', 'NOT', 'ID', 'SIGN', 'OR']) + expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) not_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) argspec_item_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'NOT', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) - atom_rsts_ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR']) + atom_rsts_ = set(['LPAR', 'BANG_IMPORTANT', 'COLOR', 'QSTR', 'SIGN', 'NOT', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'RPAR', 'ID']) comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ']) a_expr_chks = set(['ADD', 'SUB']) - a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'COMMA', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR']) - expr_slst_rsts = set(['LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'RPAR', 'VAR', 'ADD', 'NUM', 'COMMA', 'FNCT', 'STR', 'NOT', 'SIGN', 'ID']) + a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + argspec_item_rsts_ = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', 'FNCT']) + expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","']) ### Grammar ends. diff --git a/scss/tests/files/general/007-maps-example.css b/scss/tests/files/general/007-maps-example.css new file mode 100644 index 0000000..fea3c4d --- /dev/null +++ b/scss/tests/files/general/007-maps-example.css @@ -0,0 +1,3 @@ +div h1 { + color: #F4FAC7; +} diff --git a/scss/tests/files/general/007-maps-example.scss b/scss/tests/files/general/007-maps-example.scss new file mode 100644 index 0000000..5dfce63 --- /dev/null +++ b/scss/tests/files/general/007-maps-example.scss @@ -0,0 +1,24 @@ +// Taken from the Ruby documentation +$themes: ( + mist: ( + header: #DCFAC0, + text: #00968B, + border: #85C79C + ), + spring: ( + header: #F4FAC7, + text: #C2454E, + border: #FFB158 + ), + // ... +); + +@mixin themed-header($theme-name) { + h1 { + color: map-get(map-get($themes, $theme-name), header); + } +} + +div { + @include themed-header(spring); +} diff --git a/scss/tests/functions/test_core.py b/scss/tests/functions/test_core.py index ce74da6..c19f873 100644 --- a/scss/tests/functions/test_core.py +++ b/scss/tests/functions/test_core.py @@ -298,6 +298,14 @@ def test_index(calc): # ------------------------------------------------------------------------------ +# Map functions + + +# ... + + + +# ------------------------------------------------------------------------------ # Introspection functions def test_type_of(calc): diff --git a/scss/types.py b/scss/types.py index d2537a5..afe65f4 100644 --- a/scss/types.py +++ b/scss/types.py @@ -680,8 +680,6 @@ class Color(Value): class String(Value): - sass_type_name = u'string' - """Represents both CSS quoted string values and CSS identifiers (such as `left`). @@ -690,6 +688,8 @@ class String(Value): Otherwise, double quotes are used. """ + sass_type_name = u'string' + def __init__(self, value, quotes='"'): if isinstance(value, String): # TODO unclear if this should be here, but many functions rely on @@ -758,6 +758,34 @@ class String(Value): return self.__str__() +### XXX EXPERIMENTAL XXX +class Map(Value): + sass_type_name = u'map' + + def __init__(self, pairs): + self.pairs = pairs + self.index = {} + for key, value in pairs: + self.index[key] = value + + def __hash__(self): + return hash(self.pairs) + + def __len__(self): + return len(self.pairs) + + def __iter__(self): + return iter(self.pairs) + + def get_by_key(self, key): + return self.index[key] + + def get_by_pos(self, key): + return self.pairs[key][1] + + def render(self, compress=False): + raise TypeError("maps cannot be rendered as CSS") + # Backwards-compatibility. ColorValue = Color ListValue = List |