Indented syntax parser.

This commit is contained in:
Natalie Weizenbaum 2016-09-10 17:15:49 -07:00 committed by Natalie Weizenbaum
parent bca0907897
commit 5d71ac78b9
8 changed files with 1827 additions and 1514 deletions

View File

@ -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);

View File

@ -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

View File

@ -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();

View File

@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@ -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) =>