diff options
Diffstat (limited to 'scss/grammar/expression.g')
-rw-r--r-- | scss/grammar/expression.g | 107 |
1 files changed, 93 insertions, 14 deletions
diff --git a/scss/grammar/expression.g b/scss/grammar/expression.g index 9cc833d..525db2e 100644 --- a/scss/grammar/expression.g +++ b/scss/grammar/expression.g @@ -14,11 +14,13 @@ from scss.ast import AnyOp from scss.ast import AllOp from scss.ast import NotOp from scss.ast import CallOp -from scss.ast import Variable +from scss.ast import Interpolation from scss.ast import Literal +from scss.ast import Variable from scss.ast import ListLiteral from scss.ast import MapLiteral from scss.ast import ArgspecLiteral +from scss.cssdefs import unescape from scss.types import Color from scss.types import Number from scss.types import String @@ -51,6 +53,11 @@ parser SassExpression: token LT: "<" token GT: ">" token DOTDOTDOT: '[.]{3}' + token SINGLE_QUOTE: "'" + token DOUBLE_QUOTE: '"' + # Don't allow quotes or # unless they're escaped (or the # is alone) + token SINGLE_STRING_GUTS: '([^\'\\\\#]|[\\\\].|#(?![{]))*' + token DOUBLE_STRING_GUTS: "([^\"\\\\#]|[\\\\].|#(?![{]))*" token KWSTR: "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'(?=\s*:)" token STR: "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'" token KWQSTR: '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"(?=\s*:)' @@ -65,13 +72,19 @@ parser SassExpression: 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_]*" + # TODO Ruby is a bit more flexible here, for example allowing 1#{2}px + token BAREWORD: "[-a-zA-Z_][-a-zA-Z0-9_]*" token BANG_IMPORTANT: "!important" + + token INTERP_START: "#[{]" + token INTERP_END: "[}]" + token INTERP_ANYTHING: "([^#]|#(?![{]))*" # http://dev.w3.org/csswg/css-syntax-3/#consume-a-url-token0 - # URLs may not contain quotes, parentheses, or unprintables + # Bare URLs may not contain quotes, parentheses, or unprintables. Quoted + # URLs may, of course, contain whatever they like. # TODO reify escapes, for this and for strings # FIXME: Also, URLs may not contain $ as it breaks urls with variables? - token URL: "(?:[\\\\].|[^$'\"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f])*" + token BAREURL: "(?:[\\\\].|[^#$'\"()\\x00-\\x08\\x0b\\x0e-\\x1f\\x7f]|#(?![{]))*" # Goals: rule goal: expr_lst END {{ return expr_lst }} @@ -188,23 +201,19 @@ parser SassExpression: # 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)) }} + | "url" LPAR interpolated_url RPAR + {{ print("url!"); return interpolated_url }} | FNCT LPAR argspec RPAR {{ return CallOp(FNCT, argspec) }} | BANG_IMPORTANT {{ return Literal(String(BANG_IMPORTANT, quotes=None)) }} - | ID {{ return Literal.from_bareword(ID) }} + | interpolated_bareword {{ return Interpolation.maybe(interpolated_bareword) }} | NUM {{ UNITS = None }} [ UNITS ] {{ return Literal(Number(float(NUM), unit=UNITS)) }} - | STR {{ return Literal(String(dequote(STR), quotes="'")) }} - | QSTR {{ return Literal(String(dequote(QSTR), quotes='"')) }} + | interpolated_string {{ return interpolated_string }} | COLOR {{ return Literal(Color.from_hex(COLOR, literal=True)) }} | VAR {{ return Variable(VAR) }} + # TODO none of these things respect interpolation -- would love to not need + # to repeat all the rules rule kwatom: # nothing | KWID {{ return Literal.from_bareword(KWID) }} @@ -215,4 +224,74 @@ parser SassExpression: | KWCOLOR {{ return Literal(Color.from_hex(KWCOLOR, literal=True)) }} | KWVAR {{ return Variable(KWVAR) }} + # ------------------------------------------------------------------------- + # Interpolation, which is a right mess, because it depends very heavily on + # context -- what other characters are allowed, and when do we stop? + # Thankfully these rules all look pretty similar: there's a delimiter, a + # literal, and some number of interpolations and trailing literals. + rule interpolation: + INTERP_START + expr_lst + INTERP_END {{ return expr_lst }} + + rule interpolated_url: + # Note: This rule DOES NOT include the url(...) delimiters + interpolated_bare_url + {{ return Interpolation.maybe(interpolated_bare_url, type=Url, quotes=None) }} + | interpolated_string_single + {{ return Interpolation.maybe(interpolated_string_single, type=Url, quotes="'") }} + | interpolated_string_double + {{ return Interpolation.maybe(interpolated_string_double, type=Url, quotes='"') }} + + rule interpolated_bare_url: + BAREURL {{ parts = [unescape(BAREURL)] }} + ( + interpolation {{ parts.append(interpolation) }} + BAREURL {{ parts.append(unescape(BAREURL)) }} + )* {{ return parts }} + + rule interpolated_string: + interpolated_string_single + {{ return Interpolation.maybe(interpolated_string_single, quotes="'") }} + | interpolated_string_double + {{ return Interpolation.maybe(interpolated_string_double, quotes='"') }} + + rule interpolated_string_single: + SINGLE_QUOTE + SINGLE_STRING_GUTS {{ parts = [unescape(SINGLE_STRING_GUTS)] }} + ( + interpolation {{ parts.append(interpolation) }} + SINGLE_STRING_GUTS {{ parts.append(unescape(SINGLE_STRING_GUTS)) }} + )* + SINGLE_QUOTE {{ return parts }} + + rule interpolated_string_double: + DOUBLE_QUOTE + DOUBLE_STRING_GUTS {{ parts = [unescape(DOUBLE_STRING_GUTS)] }} + ( + interpolation {{ parts.append(interpolation) }} + DOUBLE_STRING_GUTS {{ parts.append(unescape(DOUBLE_STRING_GUTS)) }} + )* + DOUBLE_QUOTE {{ return parts }} + + rule interpolated_bareword: + # Again, a bareword has a fairly limited set of allowed characters + BAREWORD {{ parts = [unescape(BAREWORD)] }} + ( + interpolation {{ parts.append(interpolation) }} + BAREWORD {{ parts.append(unescape(BAREWORD)) }} + )* {{ return parts }} + + + rule goal_interpolated_anything: + # This isn't part of the grammar, but rather a separate goal, used for + # text that might contain interpolations but should not be parsed + # outside of them -- e.g., selector strings. + INTERP_ANYTHING {{ parts = [INTERP_ANYTHING] }} + ( + interpolation {{ parts.append(interpolation) }} + INTERP_ANYTHING {{ parts.append(INTERP_ANYTHING) }} + )* + END {{ return Interpolation.maybe(parts) }} + %% |