summaryrefslogtreecommitdiff
path: root/scss/grammar/expression.g
diff options
context:
space:
mode:
Diffstat (limited to 'scss/grammar/expression.g')
-rw-r--r--scss/grammar/expression.g107
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) }}
+
%%