mirror of
https://github.com/danog/dart-sass.git
synced 2024-11-30 04:39:03 +01:00
Indented syntax parser.
This commit is contained in:
parent
bca0907897
commit
5d71ac78b9
@ -35,8 +35,8 @@ void main(List<String> args) {
|
||||
|
||||
try {
|
||||
var file = options.rest.first;
|
||||
var sassTree =
|
||||
parseScss(new File(file).readAsStringSync(), url: p.toUri(file));
|
||||
var parse = p.extension(file) == '.sass' ? parseSass : parseScss;
|
||||
var sassTree = parse(new File(file).readAsStringSync(), url: p.toUri(file));
|
||||
var cssTree = new PerformVisitor().visitStylesheet(sassTree);
|
||||
var css = toCss(cssTree);
|
||||
if (css.isNotEmpty) print(css);
|
||||
|
@ -57,11 +57,16 @@ Dart Sass for developers familiar with Ruby Sass.
|
||||
several characters if necessary) and chooses which production to consume
|
||||
based on those.
|
||||
|
||||
7. The environment uses an array of maps to track variable (and eventually
|
||||
7. The indented syntax parser and the SCSS parser are subclasses of the same
|
||||
superclass. This substantially reduces the amount of duplicated code between
|
||||
the two, and makes it easier to give the indented parser good error messaging
|
||||
and source span tracking.
|
||||
|
||||
8. The environment uses an array of maps to track variable (and eventually
|
||||
function and mixin) definitions. This requires fewer allocations and produces
|
||||
more cache locality.
|
||||
|
||||
8. Because extension is done during the creation of the CSS AST, it works
|
||||
9. Because extension is done during the creation of the CSS AST, it works
|
||||
differently than the Ruby implementation. Ruby builds a collection of all
|
||||
`@extend` directives, and then iterates over the tree applying them to each
|
||||
selector as applicable. The perform visitor has similar behavior when
|
||||
@ -87,6 +92,10 @@ official behavior.
|
||||
|
||||
4. The numeric precision is set to 10. See [issue 1122][].
|
||||
|
||||
5. The indented syntax parser is more flexible: it doesn't require consistent
|
||||
indentation across the whole document. This doesn't have an issue yet; I need
|
||||
to talk to Chris to determine if it's actually the right way forward.
|
||||
|
||||
[issue 1599]: https://github.com/sass/sass/issues/1599
|
||||
[issue 1126]: https://github.com/sass/sass/issues/1126
|
||||
[issue 2120]: https://github.com/sass/sass/issues/2120
|
||||
|
@ -5,9 +5,13 @@
|
||||
import 'ast/sass.dart';
|
||||
import 'ast/selector.dart';
|
||||
import 'parse/at_root_query.dart';
|
||||
import 'parse/sass.dart';
|
||||
import 'parse/scss.dart';
|
||||
import 'parse/selector.dart';
|
||||
|
||||
Stylesheet parseSass(String contents, {url}) =>
|
||||
new SassParser(contents, url: url).parse();
|
||||
|
||||
Stylesheet parseScss(String contents, {url}) =>
|
||||
new ScssParser(contents, url: url).parse();
|
||||
|
||||
|
@ -52,7 +52,9 @@ abstract class Parser {
|
||||
|
||||
void silentComment() {
|
||||
scanner.expect("//");
|
||||
while (!scanner.isDone && !isNewline(scanner.readChar())) {}
|
||||
while (!scanner.isDone && !isNewline(scanner.peekChar())) {
|
||||
scanner.readChar();
|
||||
}
|
||||
}
|
||||
|
||||
void loudComment() {
|
||||
@ -263,6 +265,13 @@ abstract class Parser {
|
||||
: actual == expected;
|
||||
}
|
||||
|
||||
bool scanCharIf(bool condition(int character)) {
|
||||
var next = scanner.peekChar();
|
||||
if (!condition(next)) return false;
|
||||
scanner.readChar();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool scanCharIgnoreCase(int letter) {
|
||||
if (!equalsLetterIgnoreCase(letter, scanner.peekChar())) return false;
|
||||
scanner.readChar();
|
||||
|
245
lib/src/parse/sass.dart
Normal file
245
lib/src/parse/sass.dart
Normal file
@ -0,0 +1,245 @@
|
||||
// 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:string_scanner/string_scanner.dart';
|
||||
|
||||
import '../ast/sass.dart';
|
||||
import '../util/character.dart';
|
||||
import 'stylesheet.dart';
|
||||
|
||||
class SassParser extends StylesheetParser {
|
||||
var _currentIndentation = 0;
|
||||
int _nextIndentation;
|
||||
LineScannerState _nextIndentationEnd;
|
||||
bool _spaces;
|
||||
|
||||
bool get indented => true;
|
||||
|
||||
SassParser(String contents, {url}) : super(contents, url: url);
|
||||
|
||||
bool atEndOfStatement() {
|
||||
var next = scanner.peekChar();
|
||||
return next == null || isNewline(next);
|
||||
}
|
||||
|
||||
bool lookingAtChildren() =>
|
||||
atEndOfStatement() && _peekIndentation() > _currentIndentation;
|
||||
|
||||
List<Statement> children(Statement child()) {
|
||||
var children = <Statement>[];
|
||||
_whileIndentedLower(() {
|
||||
children.add(_child(child));
|
||||
});
|
||||
return children;
|
||||
}
|
||||
|
||||
List<Statement> statements(Statement statement()) {
|
||||
var first = scanner.peekChar();
|
||||
if (first == $tab || first == $space) {
|
||||
scanner.error("Indenting at the beginning of the document is illegal.",
|
||||
position: 0, length: scanner.position);
|
||||
}
|
||||
|
||||
var statements = <Statement>[];
|
||||
while (!scanner.isDone) {
|
||||
statements.add(_child(statement));
|
||||
var indentation = _readIndentation();
|
||||
assert(indentation == 0);
|
||||
}
|
||||
return statements;
|
||||
}
|
||||
|
||||
Statement _child(Statement child()) {
|
||||
switch (scanner.peekChar()) {
|
||||
case $dollar:
|
||||
return variableDeclaration();
|
||||
break;
|
||||
|
||||
case $slash:
|
||||
switch (scanner.peekChar(1)) {
|
||||
case $slash:
|
||||
return _silentComment();
|
||||
break;
|
||||
case $asterisk:
|
||||
return _loudComment();
|
||||
break;
|
||||
default:
|
||||
return child();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
return child();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Comment _silentComment() {
|
||||
var start = scanner.state;
|
||||
scanner.expect("//");
|
||||
|
||||
var buffer = new StringBuffer();
|
||||
var parentIndentation = _currentIndentation;
|
||||
while (true) {
|
||||
buffer.write("//");
|
||||
|
||||
// Skip the first two indentation characters because we're already writing
|
||||
// "//".
|
||||
for (var i = 2; i < _currentIndentation - parentIndentation; i++) {
|
||||
buffer.writeCharCode($space);
|
||||
}
|
||||
|
||||
while (!scanner.isDone && !isNewline(scanner.peekChar())) {
|
||||
buffer.writeCharCode(scanner.readChar());
|
||||
}
|
||||
buffer.writeln();
|
||||
|
||||
if (_peekIndentation() <= parentIndentation) break;
|
||||
_readIndentation();
|
||||
}
|
||||
|
||||
return new Comment(buffer.toString(), scanner.spanFrom(start),
|
||||
silent: true);
|
||||
}
|
||||
|
||||
Comment _loudComment() {
|
||||
var start = scanner.state;
|
||||
scanner.expect("/*");
|
||||
|
||||
var first = true;
|
||||
var buffer = new StringBuffer("/*");
|
||||
var parentIndentation = _currentIndentation;
|
||||
while (true) {
|
||||
if (!first) {
|
||||
buffer.writeln();
|
||||
buffer.write(" * ");
|
||||
}
|
||||
first = false;
|
||||
|
||||
for (var i = 3; i < _currentIndentation - parentIndentation; i++) {
|
||||
buffer.writeCharCode($space);
|
||||
}
|
||||
|
||||
while (!scanner.isDone && !isNewline(scanner.peekChar())) {
|
||||
buffer.writeCharCode(scanner.readChar());
|
||||
}
|
||||
|
||||
if (_peekIndentation() <= parentIndentation) break;
|
||||
_readIndentation();
|
||||
}
|
||||
buffer.write(" */");
|
||||
|
||||
print(buffer);
|
||||
return new Comment(buffer.toString(), scanner.spanFrom(start),
|
||||
silent: false);
|
||||
}
|
||||
|
||||
// Doesn't consume newlines, doesn't support comments.
|
||||
void whitespace() {
|
||||
while (!scanner.isDone) {
|
||||
var next = scanner.peekChar();
|
||||
if (next != $tab && next != $space) break;
|
||||
scanner.readChar();
|
||||
}
|
||||
|
||||
if (scanner.peekChar() == $slash && scanner.peekChar(1) == $slash) {
|
||||
silentComment();
|
||||
}
|
||||
}
|
||||
|
||||
void _whileIndentedLower(void body()) {
|
||||
var parentIndentation = _currentIndentation;
|
||||
int childIndentation;
|
||||
while (_peekIndentation() > parentIndentation) {
|
||||
var indentation = _readIndentation();
|
||||
childIndentation ??= indentation;
|
||||
if (childIndentation != indentation) {
|
||||
scanner.error(
|
||||
"Inconsistent indentation, expected $childIndentation spaces.",
|
||||
position: scanner.position - scanner.column,
|
||||
length: scanner.column);
|
||||
}
|
||||
|
||||
body();
|
||||
}
|
||||
}
|
||||
|
||||
int _readIndentation() {
|
||||
if (_nextIndentation == null) _peekIndentation();
|
||||
_currentIndentation = _nextIndentation;
|
||||
scanner.state = _nextIndentationEnd;
|
||||
_nextIndentation = null;
|
||||
_nextIndentationEnd = null;
|
||||
return _currentIndentation;
|
||||
}
|
||||
|
||||
int _peekIndentation() {
|
||||
if (_nextIndentation != null) return _nextIndentation;
|
||||
|
||||
if (scanner.isDone) {
|
||||
_nextIndentation = 0;
|
||||
_nextIndentationEnd = scanner.state;
|
||||
return 0;
|
||||
}
|
||||
|
||||
var start = scanner.state;
|
||||
if (!scanCharIf(isNewline)) {
|
||||
scanner.error("Expected newline.", position: scanner.position);
|
||||
}
|
||||
|
||||
bool containsTab;
|
||||
bool containsSpace;
|
||||
do {
|
||||
containsTab = false;
|
||||
containsSpace = false;
|
||||
_nextIndentation = 0;
|
||||
|
||||
while (true) {
|
||||
var next = scanner.peekChar();
|
||||
if (next == $space) {
|
||||
containsSpace = true;
|
||||
} else if (next == $tab) {
|
||||
containsTab = true;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
_nextIndentation++;
|
||||
scanner.readChar();
|
||||
}
|
||||
|
||||
if (scanner.isDone) {
|
||||
_nextIndentation = 0;
|
||||
_nextIndentationEnd = scanner.state;
|
||||
scanner.state = start;
|
||||
return 0;
|
||||
}
|
||||
} while (scanCharIf(isNewline));
|
||||
|
||||
_checkIndentationConsistency(containsTab, containsSpace);
|
||||
|
||||
_spaces ??= containsSpace;
|
||||
_nextIndentationEnd = scanner.state;
|
||||
scanner.state = start;
|
||||
return _nextIndentation;
|
||||
}
|
||||
|
||||
void _checkIndentationConsistency(bool containsTab, bool containsSpace) {
|
||||
if (containsTab) {
|
||||
if (containsSpace) {
|
||||
scanner.error("Tabs and spaces may not be mixed.",
|
||||
position: scanner.position - scanner.column,
|
||||
length: scanner.column);
|
||||
} else if (_spaces == true) {
|
||||
scanner.error("Expected spaces, was tabs.",
|
||||
position: scanner.position - scanner.column,
|
||||
length: scanner.column);
|
||||
}
|
||||
} else if (_spaces == false) {
|
||||
scanner.error("Expected tabs, was spaces.",
|
||||
position: scanner.position - scanner.column, length: scanner.column);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
1511
lib/src/parse/stylesheet.dart
Normal file
1511
lib/src/parse/stylesheet.dart
Normal file
File diff suppressed because it is too large
Load Diff
@ -358,8 +358,10 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
throw _exception("Can't find file to import.", node.span);
|
||||
}
|
||||
|
||||
return _importedFiles.putIfAbsent(path,
|
||||
() => parseScss(new File(path).readAsStringSync(), url: p.toUri(path)));
|
||||
return _importedFiles.putIfAbsent(path, () {
|
||||
var parse = p.extension(path) == '.sass' ? parseSass : parseScss;
|
||||
return parse(new File(path).readAsStringSync(), url: p.toUri(path));
|
||||
});
|
||||
}
|
||||
|
||||
String _tryImportPathWithExtensions(String path) =>
|
||||
|
Loading…
Reference in New Issue
Block a user