mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-26 20:24:42 +01:00
Split the parser up.
There are now separate parsers for selectors and at-root queries, since those are parsed independently of the main stylesheet. The Parser class contains utilities that are useful across different parsers.
This commit is contained in:
parent
3329ba80b8
commit
3a2b7ca9df
@ -9,7 +9,7 @@ import 'package:stack_trace/stack_trace.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'package:sass/src/exception.dart';
|
||||
import 'package:sass/src/parser.dart';
|
||||
import 'package:sass/src/parse.dart';
|
||||
import 'package:sass/src/visitor/perform.dart';
|
||||
import 'package:sass/src/visitor/serialize.dart';
|
||||
|
||||
@ -35,9 +35,9 @@ void main(List<String> args) {
|
||||
|
||||
try {
|
||||
var file = options.rest.first;
|
||||
var parser =
|
||||
new Parser(new File(file).readAsStringSync(), url: p.toUri(file));
|
||||
var cssTree = new PerformVisitor().visitStylesheet(parser.parse());
|
||||
var sassTree =
|
||||
parseScss(new File(file).readAsStringSync(), url: p.toUri(file));
|
||||
var cssTree = new PerformVisitor().visitStylesheet(sassTree);
|
||||
var css = toCss(cssTree);
|
||||
if (css.isNotEmpty) print(css);
|
||||
} on SassException catch (error, stackTrace) {
|
||||
|
21
lib/src/parse.dart
Normal file
21
lib/src/parse.dart
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'ast/sass.dart';
|
||||
import 'ast/selector.dart';
|
||||
import 'parse/at_root_query.dart';
|
||||
import 'parse/scss.dart';
|
||||
import 'parse/selector.dart';
|
||||
|
||||
Stylesheet parseScss(String contents, {url}) =>
|
||||
new ScssParser(contents, url: url).parse();
|
||||
|
||||
SelectorList parseSelector(String contents, {url}) =>
|
||||
new SelectorParser(contents, url: url).parse();
|
||||
|
||||
SimpleSelector parseSimpleSelector(String contents, {url}) =>
|
||||
new SelectorParser(contents, url: url).parseSimpleSelector();
|
||||
|
||||
AtRootQuery parseAtRootQuery(String contents, {url}) =>
|
||||
new AtRootQueryParser(contents, url: url).parse();
|
32
lib/src/parse/at_root_query.dart
Normal file
32
lib/src/parse/at_root_query.dart
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:charcode/charcode.dart';
|
||||
|
||||
import '../ast/sass.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
class AtRootQueryParser extends Parser {
|
||||
AtRootQueryParser(String contents, {url}) : super(contents, url: url);
|
||||
|
||||
AtRootQuery parse() {
|
||||
return wrapFormatException(() {
|
||||
scanner.expectChar($lparen);
|
||||
ignoreComments();
|
||||
expectIdentifier("with", ignoreCase: true);
|
||||
var include = !scanIdentifier("out", ignoreCase: true);
|
||||
ignoreComments();
|
||||
scanner.expectChar($colon);
|
||||
ignoreComments();
|
||||
|
||||
var atRules = new Set<String>();
|
||||
do {
|
||||
atRules.add(identifier().toLowerCase());
|
||||
ignoreComments();
|
||||
} while (lookingAtIdentifier());
|
||||
|
||||
return new AtRootQuery(include, atRules);
|
||||
});
|
||||
}
|
||||
}
|
346
lib/src/parse/parser.dart
Normal file
346
lib/src/parse/parser.dart
Normal file
@ -0,0 +1,346 @@
|
||||
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:charcode/charcode.dart';
|
||||
import 'package:source_span/source_span.dart';
|
||||
import 'package:string_scanner/string_scanner.dart';
|
||||
|
||||
import '../exception.dart';
|
||||
import '../util/character.dart';
|
||||
|
||||
abstract class Parser {
|
||||
final SpanScanner scanner;
|
||||
|
||||
Parser(String contents, {url})
|
||||
: scanner = new SpanScanner(contents, sourceUrl: url);
|
||||
|
||||
// ## Tokens
|
||||
|
||||
String commentText() => rawText(ignoreComments);
|
||||
|
||||
bool scanWhitespace() {
|
||||
var start = scanner.position;
|
||||
ignoreComments();
|
||||
return scanner.position != start;
|
||||
}
|
||||
|
||||
void ignoreComments() {
|
||||
do {
|
||||
whitespace();
|
||||
} while (comment());
|
||||
}
|
||||
|
||||
void whitespace() {
|
||||
while (!scanner.isDone && isWhitespace(scanner.peekChar())) {
|
||||
scanner.readChar();
|
||||
}
|
||||
}
|
||||
|
||||
bool comment() {
|
||||
if (scanner.peekChar() != $slash) return false;
|
||||
|
||||
var next = scanner.peekChar(1);
|
||||
if (next == $slash) {
|
||||
silentComment();
|
||||
return true;
|
||||
} else if (next == $asterisk) {
|
||||
loudComment();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void silentComment() {
|
||||
scanner.expect("//");
|
||||
while (!scanner.isDone && !isNewline(scanner.readChar())) {}
|
||||
}
|
||||
|
||||
void loudComment() {
|
||||
scanner.expect("/*");
|
||||
do {
|
||||
while (scanner.readChar() != $asterisk) {}
|
||||
} while (scanner.readChar() != $slash);
|
||||
}
|
||||
|
||||
String identifier() {
|
||||
var text = new StringBuffer();
|
||||
while (scanner.scanChar($dash)) {
|
||||
text.writeCharCode($dash);
|
||||
}
|
||||
|
||||
var first = scanner.peekChar();
|
||||
if (first == null) {
|
||||
scanner.error("Expected identifier.");
|
||||
} else if (isNameStart(first)) {
|
||||
text.writeCharCode(scanner.readChar());
|
||||
} else if (first == $backslash) {
|
||||
text.writeCharCode(escape());
|
||||
} else {
|
||||
scanner.error("Expected identifier.");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
var next = scanner.peekChar();
|
||||
if (next == null) {
|
||||
break;
|
||||
} else if (isName(next)) {
|
||||
text.writeCharCode(scanner.readChar());
|
||||
} else if (next == $backslash) {
|
||||
text.writeCharCode(escape());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
String string() {
|
||||
// NOTE: this logic is largely duplicated in ScssParser._interpolatedString.
|
||||
// Most changes here should be mirrored there.
|
||||
|
||||
var quote = scanner.readChar();
|
||||
if (quote != $single_quote && quote != $double_quote) {
|
||||
scanner.error("Expected string.",
|
||||
position: quote == null ? scanner.position : scanner.position - 1);
|
||||
}
|
||||
|
||||
var buffer = new StringBuffer();
|
||||
while (true) {
|
||||
var next = scanner.peekChar();
|
||||
if (next == quote) {
|
||||
scanner.readChar();
|
||||
break;
|
||||
} else if (next == null || isNewline(next)) {
|
||||
scanner.error("Expected ${new String.fromCharCode(quote)}.");
|
||||
} else if (next == $backslash) {
|
||||
if (isNewline(scanner.peekChar(1))) {
|
||||
scanner.readChar();
|
||||
scanner.readChar();
|
||||
} else {
|
||||
buffer.writeCharCode(escape());
|
||||
}
|
||||
} else {
|
||||
buffer.writeCharCode(scanner.readChar());
|
||||
}
|
||||
}
|
||||
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String declarationValue() {
|
||||
// NOTE: this logic is largely duplicated in
|
||||
// ScssParser._interpolatedDeclarationValue. Most changes here should be
|
||||
// mirrored there.
|
||||
|
||||
var buffer = new StringBuffer();
|
||||
var brackets = <int>[];
|
||||
var wroteNewline = false;
|
||||
loop:
|
||||
while (true) {
|
||||
var next = scanner.peekChar();
|
||||
switch (next) {
|
||||
case $backslash:
|
||||
buffer.writeCharCode(escape());
|
||||
wroteNewline = false;
|
||||
break;
|
||||
|
||||
case $double_quote:
|
||||
case $single_quote:
|
||||
buffer.write(rawText(string));
|
||||
wroteNewline = false;
|
||||
break;
|
||||
|
||||
case $slash:
|
||||
if (scanner.peekChar(1) == $asterisk) {
|
||||
buffer.write(rawText(loudComment));
|
||||
} else {
|
||||
buffer.writeCharCode(scanner.readChar());
|
||||
}
|
||||
wroteNewline = false;
|
||||
break;
|
||||
|
||||
case $space:
|
||||
case $tab:
|
||||
if (wroteNewline || !isWhitespace(scanner.peekChar(1))) {
|
||||
buffer.writeCharCode($space);
|
||||
}
|
||||
scanner.readChar();
|
||||
break;
|
||||
|
||||
case $lf:
|
||||
case $cr:
|
||||
case $ff:
|
||||
if (!isNewline(scanner.peekChar(-1))) buffer.writeln();
|
||||
scanner.readChar();
|
||||
wroteNewline = true;
|
||||
break;
|
||||
|
||||
case $lparen:
|
||||
case $lbrace:
|
||||
case $lbracket:
|
||||
buffer.writeCharCode(next);
|
||||
brackets.add(opposite(scanner.readChar()));
|
||||
wroteNewline = false;
|
||||
break;
|
||||
|
||||
case $rparen:
|
||||
case $rbrace:
|
||||
case $rbracket:
|
||||
if (brackets.isEmpty) break loop;
|
||||
buffer.writeCharCode(next);
|
||||
scanner.expectChar(brackets.removeLast());
|
||||
wroteNewline = false;
|
||||
break;
|
||||
|
||||
case $exclamation:
|
||||
case $semicolon:
|
||||
break loop;
|
||||
|
||||
default:
|
||||
if (next == null) break loop;
|
||||
|
||||
// TODO: support url()
|
||||
buffer.writeCharCode(scanner.readChar());
|
||||
wroteNewline = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (brackets.isNotEmpty) scanner.expectChar(brackets.last);
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String variableName() {
|
||||
scanner.expectChar($dollar);
|
||||
return identifier();
|
||||
}
|
||||
|
||||
// ## Characters
|
||||
|
||||
int escape() {
|
||||
// See https://drafts.csswg.org/css-syntax-3/#consume-escaped-code-point.
|
||||
|
||||
scanner.expectChar($backslash);
|
||||
var first = scanner.peekChar();
|
||||
if (first == null) {
|
||||
return 0xFFFD;
|
||||
} else if (isNewline(first)) {
|
||||
scanner.error("Expected escape sequence.");
|
||||
return 0;
|
||||
} else if (isHex(first)) {
|
||||
var value = 0;
|
||||
for (var i = 0; i < 6; i++) {
|
||||
var next = scanner.peekChar();
|
||||
if (next == null || !isHex(next)) break;
|
||||
value = (value << 4) + asHex(scanner.readChar());
|
||||
}
|
||||
if (isWhitespace(scanner.peekChar())) scanner.readChar();
|
||||
|
||||
if (value == 0 ||
|
||||
(value >= 0xD800 && value <= 0xDFFF) ||
|
||||
value >= 0x10FFFF) {
|
||||
return 0xFFFD;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
return scanner.readChar();
|
||||
}
|
||||
}
|
||||
|
||||
int readCharOrEscape() {
|
||||
var next = scanner.readChar();
|
||||
return next == $backslash ? escape() : next;
|
||||
}
|
||||
|
||||
bool scanCharOrEscape(int expected, {bool ignoreCase: false}) {
|
||||
// TODO(nweiz): Test if it's faster to split this into separate methods for
|
||||
// case-sensitivity rather than checking the boolean each time.
|
||||
var actual = readCharOrEscape();
|
||||
return ignoreCase
|
||||
? characterEqualsIgnoreCase(actual, expected)
|
||||
: actual == expected;
|
||||
}
|
||||
|
||||
bool scanCharIgnoreCase(int letter) {
|
||||
if (!equalsLetterIgnoreCase(letter, scanner.peekChar())) return false;
|
||||
scanner.readChar();
|
||||
return true;
|
||||
}
|
||||
|
||||
void expectCharIgnoreCase(int letter) {
|
||||
var actual = scanner.readChar();
|
||||
if (equalsLetterIgnoreCase(letter, actual)) return;
|
||||
|
||||
scanner.error('Expected "${new String.fromCharCode(letter)}".',
|
||||
position: actual == null ? scanner.position : scanner.position - 1);
|
||||
}
|
||||
|
||||
// ## Utilities
|
||||
|
||||
/// This is based on [the CSS algorithm][], but it assumes all backslashes
|
||||
/// start escapes.
|
||||
///
|
||||
/// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier
|
||||
bool lookingAtIdentifier() {
|
||||
// See also [ScssParser._lookingAtInterpolatedIdentifier].
|
||||
|
||||
var first = scanner.peekChar();
|
||||
if (isNameStart(first) || first == $backslash) return true;
|
||||
|
||||
if (first != $dash) return false;
|
||||
var second = scanner.peekChar(1);
|
||||
return isNameStart(second) || second == $dash || second == $backslash;
|
||||
}
|
||||
|
||||
bool scanIdentifier(String text, {bool ignoreCase: false}) {
|
||||
if (!lookingAtIdentifier()) return false;
|
||||
|
||||
var start = scanner.state;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
var next = text.codeUnitAt(i);
|
||||
if (scanCharOrEscape(next, ignoreCase: ignoreCase)) continue;
|
||||
scanner.state = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
var next = scanner.peekChar();
|
||||
if (next == null) return true;
|
||||
if (!isName(next) && next != $backslash) return true;
|
||||
scanner.state = start;
|
||||
return false;
|
||||
}
|
||||
|
||||
void expectIdentifier(String text, {String name, bool ignoreCase: false}) {
|
||||
name ??= '"$text"';
|
||||
|
||||
var start = scanner.position;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
var next = text.codeUnitAt(i);
|
||||
if (scanCharOrEscape(next, ignoreCase: ignoreCase)) continue;
|
||||
scanner.error("Expected $name.", position: start);
|
||||
}
|
||||
|
||||
var next = scanner.peekChar();
|
||||
if (next == null) return;
|
||||
if (!isName(next) && next != $backslash) return;
|
||||
scanner.error("Expected $name", position: start);
|
||||
}
|
||||
|
||||
String rawText(void consumer()) {
|
||||
var start = scanner.position;
|
||||
consumer();
|
||||
return scanner.substring(start);
|
||||
}
|
||||
|
||||
/*=T*/ wrapFormatException/*<T>*/(/*=T*/ callback()) {
|
||||
try {
|
||||
return callback();
|
||||
} on StringScannerException catch (error) {
|
||||
throw new SassException(error.message, error.span as FileSpan);
|
||||
}
|
||||
}
|
||||
}
|
1590
lib/src/parse/scss.dart
Normal file
1590
lib/src/parse/scss.dart
Normal file
File diff suppressed because it is too large
Load Diff
347
lib/src/parse/selector.dart
Normal file
347
lib/src/parse/selector.dart
Normal file
@ -0,0 +1,347 @@
|
||||
// Copyright 2016 Google Inc. Use of this source code is governed by an
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'package:charcode/charcode.dart';
|
||||
|
||||
import '../ast/selector.dart';
|
||||
import '../util/character.dart';
|
||||
import '../utils.dart';
|
||||
import 'parser.dart';
|
||||
|
||||
final _selectorPseudoClasses = new Set.from(
|
||||
["not", "matches", "current", "any", "has", "host", "host-context"]);
|
||||
|
||||
final _prefixedSelectorPseudoClasses =
|
||||
new Set.from(["nth-child", "nth-last-child"]);
|
||||
|
||||
class SelectorParser extends Parser {
|
||||
SelectorParser(String contents, {url}) : super(contents, url: url);
|
||||
|
||||
SelectorList parse() {
|
||||
return wrapFormatException(() {
|
||||
var selector = _selectorList();
|
||||
scanner.expectDone();
|
||||
return selector;
|
||||
});
|
||||
}
|
||||
|
||||
SimpleSelector parseSimpleSelector() {
|
||||
return wrapFormatException(() {
|
||||
var simple = _simpleSelector();
|
||||
scanner.expectDone();
|
||||
return simple;
|
||||
});
|
||||
}
|
||||
|
||||
SelectorList _selectorList() {
|
||||
var components = <ComplexSelector>[];
|
||||
var lineBreaks = <int>[];
|
||||
|
||||
ignoreComments();
|
||||
var previousLine = scanner.line;
|
||||
do {
|
||||
ignoreComments();
|
||||
var next = scanner.peekChar();
|
||||
if (next == $comma) continue;
|
||||
if (next == $lbrace) break;
|
||||
|
||||
if (scanner.line != previousLine) {
|
||||
lineBreaks.add(components.length);
|
||||
previousLine = scanner.line;
|
||||
}
|
||||
components.add(_complexSelector());
|
||||
} while (scanner.scanChar($comma));
|
||||
|
||||
return new SelectorList(components, lineBreaks: lineBreaks);
|
||||
}
|
||||
|
||||
ComplexSelector _complexSelector() {
|
||||
var components = <ComplexSelectorComponent>[];
|
||||
var lineBreaks = <int>[];
|
||||
|
||||
var previousLine = scanner.line;
|
||||
loop:
|
||||
while (true) {
|
||||
ignoreComments();
|
||||
|
||||
ComplexSelectorComponent component;
|
||||
var next = scanner.peekChar();
|
||||
switch (next) {
|
||||
case $plus:
|
||||
scanner.readChar();
|
||||
component = Combinator.nextSibling;
|
||||
break;
|
||||
|
||||
case $gt:
|
||||
scanner.readChar();
|
||||
component = Combinator.child;
|
||||
break;
|
||||
|
||||
case $tilde:
|
||||
scanner.readChar();
|
||||
component = Combinator.followingSibling;
|
||||
break;
|
||||
|
||||
case $lbracket:
|
||||
case $dot:
|
||||
case $hash:
|
||||
case $percent:
|
||||
case $colon:
|
||||
case $ampersand:
|
||||
case $asterisk:
|
||||
case $pipe:
|
||||
component = _compoundSelector();
|
||||
break;
|
||||
|
||||
default:
|
||||
if (next == null || !lookingAtIdentifier()) break loop;
|
||||
component = _compoundSelector();
|
||||
break;
|
||||
}
|
||||
|
||||
if (scanner.line != previousLine) {
|
||||
lineBreaks.add(components.length);
|
||||
previousLine = scanner.line;
|
||||
}
|
||||
components.add(component);
|
||||
}
|
||||
|
||||
return new ComplexSelector(components, lineBreaks: lineBreaks);
|
||||
}
|
||||
|
||||
CompoundSelector _compoundSelector() {
|
||||
var components = <SimpleSelector>[_simpleSelector()];
|
||||
|
||||
while (isSimpleSelectorStart(scanner.peekChar())) {
|
||||
components.add(_simpleSelector(allowParent: false));
|
||||
}
|
||||
|
||||
// TODO: support "*E".
|
||||
return new CompoundSelector(components);
|
||||
}
|
||||
|
||||
SimpleSelector _simpleSelector({bool allowParent: true}) {
|
||||
switch (scanner.peekChar()) {
|
||||
case $lbracket:
|
||||
return _attributeSelector();
|
||||
case $dot:
|
||||
return _classSelector();
|
||||
case $hash:
|
||||
return _idSelector();
|
||||
case $percent:
|
||||
return _placeholderSelector();
|
||||
case $colon:
|
||||
return _pseudoSelector();
|
||||
case $ampersand:
|
||||
if (!allowParent) return _typeOrUniversalSelector();
|
||||
return _parentSelector();
|
||||
|
||||
default:
|
||||
return _typeOrUniversalSelector();
|
||||
}
|
||||
}
|
||||
|
||||
AttributeSelector _attributeSelector() {
|
||||
scanner.expectChar($lbracket);
|
||||
ignoreComments();
|
||||
|
||||
var name = _attributeName();
|
||||
ignoreComments();
|
||||
if (scanner.scanChar($rbracket)) {
|
||||
scanner.readChar();
|
||||
return new AttributeSelector(name);
|
||||
}
|
||||
|
||||
var operator = _attributeOperator();
|
||||
ignoreComments();
|
||||
|
||||
var next = scanner.peekChar();
|
||||
var value = next == $single_quote || next == $double_quote
|
||||
? string()
|
||||
: identifier();
|
||||
ignoreComments();
|
||||
|
||||
scanner.expectChar($rbracket);
|
||||
return new AttributeSelector.withOperator(name, operator, value);
|
||||
}
|
||||
|
||||
NamespacedIdentifier _attributeName() {
|
||||
if (scanner.scanChar($asterisk)) {
|
||||
scanner.expectChar($pipe);
|
||||
return new NamespacedIdentifier(identifier(), namespace: "*");
|
||||
}
|
||||
|
||||
var nameOrNamespace = identifier();
|
||||
if (scanner.peekChar() != $pipe || scanner.peekChar(1) == $equal) {
|
||||
return new NamespacedIdentifier(nameOrNamespace);
|
||||
}
|
||||
|
||||
scanner.readChar();
|
||||
return new NamespacedIdentifier(identifier(), namespace: nameOrNamespace);
|
||||
}
|
||||
|
||||
AttributeOperator _attributeOperator() {
|
||||
var start = scanner.state;
|
||||
switch (scanner.readChar()) {
|
||||
case $equal:
|
||||
return AttributeOperator.equal;
|
||||
|
||||
case $tilde:
|
||||
scanner.expectChar($equal);
|
||||
return AttributeOperator.include;
|
||||
|
||||
case $pipe:
|
||||
scanner.expectChar($equal);
|
||||
return AttributeOperator.dash;
|
||||
|
||||
case $caret:
|
||||
scanner.expectChar($equal);
|
||||
return AttributeOperator.prefix;
|
||||
|
||||
case $dollar:
|
||||
scanner.expectChar($equal);
|
||||
return AttributeOperator.suffix;
|
||||
|
||||
case $asterisk:
|
||||
scanner.expectChar($equal);
|
||||
return AttributeOperator.substring;
|
||||
|
||||
default:
|
||||
scanner.error('Expected "]".', position: start.position);
|
||||
throw "Unreachable";
|
||||
}
|
||||
}
|
||||
|
||||
ClassSelector _classSelector() {
|
||||
scanner.expectChar($dot);
|
||||
var name = identifier();
|
||||
return new ClassSelector(name);
|
||||
}
|
||||
|
||||
IDSelector _idSelector() {
|
||||
scanner.expectChar($hash);
|
||||
var name = identifier();
|
||||
return new IDSelector(name);
|
||||
}
|
||||
|
||||
PlaceholderSelector _placeholderSelector() {
|
||||
scanner.expectChar($percent);
|
||||
var name = identifier();
|
||||
return new PlaceholderSelector(name);
|
||||
}
|
||||
|
||||
ParentSelector _parentSelector() {
|
||||
scanner.expectChar($ampersand);
|
||||
var next = scanner.peekChar();
|
||||
var suffix = isName(next) || next == $backslash ? identifier() : null;
|
||||
return new ParentSelector(suffix: suffix);
|
||||
}
|
||||
|
||||
PseudoSelector _pseudoSelector() {
|
||||
scanner.expectChar($colon);
|
||||
var type = scanner.scanChar($colon) ? PseudoType.element : PseudoType.klass;
|
||||
var name = identifier();
|
||||
|
||||
if (!scanner.scanChar($lparen)) {
|
||||
return new PseudoSelector(name, type);
|
||||
}
|
||||
ignoreComments();
|
||||
|
||||
var unvendored = unvendor(name);
|
||||
String argument;
|
||||
SelectorList selector;
|
||||
if (type == PseudoType.element) {
|
||||
argument = declarationValue();
|
||||
} else if (_selectorPseudoClasses.contains(unvendored)) {
|
||||
selector = _selectorList();
|
||||
} else if (_prefixedSelectorPseudoClasses.contains(unvendored)) {
|
||||
argument = rawText(_aNPlusB);
|
||||
if (scanWhitespace()) {
|
||||
expectIdentifier("of", ignoreCase: true);
|
||||
argument += " of";
|
||||
ignoreComments();
|
||||
|
||||
selector = _selectorList();
|
||||
}
|
||||
} else {
|
||||
argument = declarationValue();
|
||||
}
|
||||
scanner.expectChar($rparen);
|
||||
|
||||
return new PseudoSelector(name, type,
|
||||
argument: argument, selector: selector);
|
||||
}
|
||||
|
||||
void _aNPlusB() {
|
||||
switch (scanner.peekChar()) {
|
||||
case $e:
|
||||
case $E:
|
||||
expectIdentifier("even", ignoreCase: true);
|
||||
return;
|
||||
|
||||
case $o:
|
||||
case $O:
|
||||
expectIdentifier("odd", ignoreCase: true);
|
||||
return;
|
||||
|
||||
case $plus:
|
||||
case $minus:
|
||||
scanner.readChar();
|
||||
break;
|
||||
}
|
||||
|
||||
var first = scanner.peekChar();
|
||||
if (first != null && isDigit(first)) {
|
||||
while (isDigit(scanner.peekChar())) {
|
||||
scanner.readChar();
|
||||
}
|
||||
ignoreComments();
|
||||
if (!scanCharIgnoreCase($n)) return;
|
||||
} else {
|
||||
expectCharIgnoreCase($n);
|
||||
}
|
||||
ignoreComments();
|
||||
|
||||
var next = scanner.peekChar();
|
||||
if (next != $plus && next != $minus) return;
|
||||
scanner.readChar();
|
||||
ignoreComments();
|
||||
|
||||
var last = scanner.peekChar();
|
||||
if (last == null || !isDigit(last)) scanner.error("Expected a number.");
|
||||
while (isDigit(scanner.peekChar())) {
|
||||
scanner.readChar();
|
||||
}
|
||||
}
|
||||
|
||||
SimpleSelector _typeOrUniversalSelector() {
|
||||
var first = scanner.peekChar();
|
||||
if (first == $asterisk) {
|
||||
scanner.readChar();
|
||||
if (!scanner.scanChar($pipe)) return new UniversalSelector();
|
||||
if (scanner.scanChar($asterisk)) {
|
||||
return new UniversalSelector(namespace: "*");
|
||||
} else {
|
||||
return new TypeSelector(
|
||||
new NamespacedIdentifier(identifier(), namespace: "*"));
|
||||
}
|
||||
} else if (first == $pipe) {
|
||||
scanner.readChar();
|
||||
if (scanner.scanChar($asterisk)) {
|
||||
return new UniversalSelector(namespace: "");
|
||||
} else {
|
||||
return new TypeSelector(
|
||||
new NamespacedIdentifier(identifier(), namespace: ""));
|
||||
}
|
||||
}
|
||||
|
||||
var nameOrNamespace = identifier();
|
||||
if (!scanner.scanChar($pipe)) {
|
||||
return new TypeSelector(new NamespacedIdentifier(nameOrNamespace));
|
||||
}
|
||||
|
||||
return new TypeSelector(
|
||||
new NamespacedIdentifier(identifier(), namespace: nameOrNamespace));
|
||||
}
|
||||
}
|
2129
lib/src/parser.dart
2129
lib/src/parser.dart
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,7 @@ import '../callable.dart';
|
||||
import '../environment.dart';
|
||||
import '../exception.dart';
|
||||
import '../extend/extender.dart';
|
||||
import '../parser.dart';
|
||||
import '../parse.dart';
|
||||
import '../utils.dart';
|
||||
import '../value.dart';
|
||||
import 'interface/statement.dart';
|
||||
@ -82,7 +82,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
void visitAtRootRule(AtRootRule node) {
|
||||
var query = node.query == null
|
||||
? AtRootQuery.defaultQuery
|
||||
: new Parser(_performInterpolation(node.query)).parseAtRootQuery();
|
||||
: parseAtRootQuery(_performInterpolation(node.query));
|
||||
|
||||
var parent = _parent;
|
||||
var included = <CssParentNode>[];
|
||||
@ -251,7 +251,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
|
||||
// TODO: recontextualize parse errors.
|
||||
// TODO: disallow parent selectors.
|
||||
var target = new Parser(targetText.value.trim()).parseSimpleSelector();
|
||||
var target = parseSimpleSelector(targetText.value.trim());
|
||||
_extender.addExtension(_selector.value, target, node);
|
||||
}
|
||||
|
||||
@ -358,10 +358,8 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
throw _exception("Can't find file to import.", node.span);
|
||||
}
|
||||
|
||||
return _importedFiles.putIfAbsent(
|
||||
path,
|
||||
() => new Parser(new File(path).readAsStringSync(), url: p.toUri(path))
|
||||
.parse());
|
||||
return _importedFiles.putIfAbsent(path,
|
||||
() => parseScss(new File(path).readAsStringSync(), url: p.toUri(path)));
|
||||
}
|
||||
|
||||
String _tryImportPathWithExtensions(String path) =>
|
||||
@ -472,7 +470,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
}
|
||||
|
||||
var selectorText = _interpolationToValue(node.selector, trim: true);
|
||||
var parsedSelector = new Parser(selectorText.value).parseSelector();
|
||||
var parsedSelector = parseSelector(selectorText.value);
|
||||
parsedSelector = _addExceptionSpan(
|
||||
() => parsedSelector.resolveParentSelectors(_selector?.value),
|
||||
node.selector.span);
|
||||
|
Loading…
Reference in New Issue
Block a user