summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEevee (Alex Munroe) <eevee.git@veekun.com>2014-07-12 18:26:38 -0700
committerEevee (Alex Munroe) <eevee.git@veekun.com>2014-07-12 18:27:42 -0700
commit7c50bb9c26e7c9988cfcdbf8ea2cd3030324bd80 (patch)
treefaf93ae2a25544e8ec176c87d600babd05beaf27
parentd26f7ed6756967360b37780b517857b067d62b88 (diff)
downloadpyscss-7c50bb9c26e7c9988cfcdbf8ea2cd3030324bd80.tar.gz
At last, real parsing for url(). Partly. #192
-rw-r--r--scss/expression.py60
-rw-r--r--scss/src/grammar/grammar.g15
-rw-r--r--scss/types.py7
3 files changed, 64 insertions, 18 deletions
diff --git a/scss/expression.py b/scss/expression.py
index bb2fd01..034a007 100644
--- a/scss/expression.py
+++ b/scss/expression.py
@@ -15,7 +15,7 @@ import scss.config as config
from scss.cssdefs import COLOR_NAMES, is_builtin_css_function, _expr_glob_re, _interpolate_re
from scss.errors import SassError, SassEvaluationError, SassParseError
from scss.rule import Namespace
-from scss.types import Boolean, Color, List, Map, Null, Number, String, Undefined, Value
+from scss.types import Boolean, Color, List, Map, Null, Number, String, Undefined, Url, Value
from scss.util import dequote, normalize_var
################################################################################
@@ -576,6 +576,9 @@ class Parser(object):
class SassExpressionScanner(Scanner):
patterns = None
_patterns = [
+ ('"\'"', "'"),
+ ('"\\""', '"'),
+ ('"url"', 'url'),
('":"', ':'),
('","', ','),
('[ \r\t\n]+', '[ \r\t\n]+'),
@@ -614,6 +617,7 @@ class SassExpressionScanner(Scanner):
('KWID', '[-a-zA-Z_][-a-zA-Z0-9_]*(?=\\s*:)'),
('ID', '[-a-zA-Z_][-a-zA-Z0-9_]*'),
('BANG_IMPORTANT', '!important'),
+ ('URL', '(?:[\\\\].|[^\'"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f])*'),
]
def __init__(self, input=None):
@@ -837,6 +841,25 @@ class SassExpression(Parser):
v = expr_lst
RPAR = self._scan('RPAR')
return Parentheses(v)
+ elif _token_ == '"url"':
+ self._scan('"url"')
+ LPAR = self._scan('LPAR')
+ _token_ = self._peek(self.atom_rsts_)
+ if _token_ == 'URL':
+ URL = self._scan('URL')
+ quotes = None
+ elif _token_ == '"\\""':
+ self._scan('"\\""')
+ URL = self._scan('URL')
+ self._scan('"\\""')
+ quotes = '"'
+ else: # == '"\'"'
+ self._scan('"\'"')
+ URL = self._scan('URL')
+ self._scan('"\'"')
+ quotes = "'"
+ RPAR = self._scan('RPAR')
+ return Literal(Url(URL, quotes=quotes))
elif _token_ == 'FNCT':
FNCT = self._scan('FNCT')
LPAR = self._scan('LPAR')
@@ -852,7 +875,7 @@ class SassExpression(Parser):
elif _token_ == 'NUM':
NUM = self._scan('NUM')
UNITS = None
- if self._peek(self.atom_rsts_) == 'UNITS':
+ if self._peek(self.atom_rsts__) == 'UNITS':
UNITS = self._scan('UNITS')
return Literal(Number(float(NUM), unit=UNITS))
elif _token_ == 'STR':
@@ -894,32 +917,33 @@ class SassExpression(Parser):
KWVAR = self._scan('KWVAR')
return Variable(KWVAR)
- 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', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
+ u_expr_chks = set(['"url"', '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', '"url"', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
argspec_items_rsts = set(['RPAR', 'END', '","'])
expr_map_rsts = set(['RPAR', '","'])
- argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
+ argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
kwatom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'KWCOLOR', '":"', 'KWNUM'])
- argspec_item_chks = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
+ argspec_item_chks = set(['"url"', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
a_expr_chks = set(['ADD', 'SUB'])
- expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","'])
- or_expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","'])
- and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","'])
- 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', '","'])
+ expr_slst_rsts = set(['"url"', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","'])
+ atom_rsts__ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
+ or_expr_rsts = set(['"url"', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","'])
+ and_expr_rsts = set(['AND', 'LPAR', 'RPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","'])
+ comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","'])
argspec_chks = set(['DOTDOTDOT', 'SLURPYVAR'])
- 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', '","'])
+ atom_rsts_ = set(['URL', '"\\""', '"\'"'])
expr_map_rsts_ = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'RPAR', 'KWCOLOR', '":"', 'KWNUM', '","'])
- u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID'])
+ u_expr_rsts = set(['"url"', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID'])
comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ'])
- argspec_items_rsts_ = set(['KWVAR', 'LPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', '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_items_rsts_ = set(['KWVAR', 'LPAR', 'RPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
+ a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', '"url"', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","'])
m_expr_chks = set(['MUL', 'DIV'])
kwatom_rsts_ = set(['UNITS', '":"'])
- argspec_items_chks = set(['KWVAR', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
- argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID'])
- atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'RPAR', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT'])
+ argspec_items_chks = set(['KWVAR', '"url"', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID'])
+ argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID'])
+ atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'RPAR', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '"url"', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT'])
argspec_chks_ = set(['END', 'RPAR'])
- argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID'])
+ argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', '"url"', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID'])
### Grammar ends.
diff --git a/scss/src/grammar/grammar.g b/scss/src/grammar/grammar.g
index 135d594..58489aa 100644
--- a/scss/src/grammar/grammar.g
+++ b/scss/src/grammar/grammar.g
@@ -40,6 +40,10 @@ parser SassExpression:
token KWID: "[-a-zA-Z_][-a-zA-Z0-9_]*(?=\s*:)"
token ID: "[-a-zA-Z_][-a-zA-Z0-9_]*"
token BANG_IMPORTANT: "!important"
+ # http://dev.w3.org/csswg/css-syntax-3/#consume-a-url-token0
+ # URLs may not contain quotes, parentheses, or unprintables
+ # TODO reify escapes, for this and for strings
+ token URL: "(?:[\\\\].|[^'\"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f])*"
# Goals:
rule goal: expr_lst END {{ return expr_lst }}
@@ -152,6 +156,17 @@ parser SassExpression:
| expr_map {{ v = expr_map }}
| expr_lst {{ v = expr_lst }}
) RPAR {{ return Parentheses(v) }}
+ # Special functions. Note that these technically overlap with the
+ # regular function rule, which makes this not quite LL -- but they're
+ # different tokens so yapps can't tell, and it resolves the conflict by
+ # picking the first one.
+ | "url" LPAR
+ (
+ URL {{ quotes = None }}
+ | "\"" URL "\"" {{ quotes = '"' }}
+ | "'" URL "'" {{ quotes = "'" }}
+ )
+ RPAR {{ return Literal(Url(URL, quotes=quotes)) }}
| FNCT LPAR argspec RPAR {{ return CallOp(FNCT, argspec) }}
| BANG_IMPORTANT {{ return Literal(String(BANG_IMPORTANT, quotes=None)) }}
| ID {{ return Literal(parse_bareword(ID)) }}
diff --git a/scss/types.py b/scss/types.py
index ef28748..27528e2 100644
--- a/scss/types.py
+++ b/scss/types.py
@@ -1062,6 +1062,13 @@ class String(Value):
return self.__str__()
+class Url(String):
+ def render(self, compress=False):
+ # TODO url-escape whatever needs escaping
+ # TODO does that mean we should un-url-escape when parsing? probably
+ return "url({0})".format(super(String, self).render(compress))
+
+
class Map(Value):
sass_type_name = six.u('map')