This commit is contained in:
Natalie Weizenbaum 2016-08-28 14:26:40 -07:00 committed by Natalie Weizenbaum
parent c4cda30bc0
commit 82aca21682
8 changed files with 113 additions and 51 deletions

View File

@ -28,6 +28,7 @@ export 'sass/statement/content.dart';
export 'sass/statement/declaration.dart';
export 'sass/statement/extend_rule.dart';
export 'sass/statement/function_declaration.dart';
export 'sass/statement/if.dart';
export 'sass/statement/include.dart';
export 'sass/statement/media_rule.dart';
export 'sass/statement/mixin_declaration.dart';

View File

@ -0,0 +1,25 @@
// 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:source_span/source_span.dart';
import '../../../visitor/interface/statement.dart';
import '../expression.dart';
import '../statement.dart';
class If implements Statement {
final Expression expression;
final List<Statement> children;
final FileSpan span;
If(this.expression, Iterable<Statement> children, {this.span})
: children = new List.unmodifiable(children);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitIf(this);
String toString() => "@if $expression {${children.join(" ")}}";
}

View File

@ -33,6 +33,8 @@ class Environment {
Environment get contentEnvironment => _contentEnvironment;
Environment _contentEnvironment;
var _inSemiGlobalScope = false;
Environment()
: _variables = [normalizedMap()],
_variableIndices = normalizedMap(),
@ -63,7 +65,8 @@ class Environment {
void setVariable(String name, Value value, {bool global: false}) {
var index = global || _variables.length == 1
? 0
: _variableIndices.putIfAbsent(name, () => _variables.length - 1);
: _variableIndices.putIfAbsent(name,
() => _inSemiGlobalScope ? 0 : _variables.length - 1);
_variables[index][name] = value;
}
@ -122,14 +125,19 @@ class Environment {
_contentEnvironment = oldEnvironment;
}
/*=T*/ scope/*<T>*/(/*=T*/ callback()) {
/*=T*/ scope/*<T>*/(/*=T*/ callback(), {bool semiGlobal: false}) {
assert(!semiGlobal || _inSemiGlobalScope || _variables.length == 1);
// TODO: avoid creating a new scope if no variables are declared.
var wasInSemiGlobalScope = _inSemiGlobalScope;
_inSemiGlobalScope = semiGlobal;
_variables.add(normalizedMap());
_functions.add(normalizedMap());
_mixins.add(normalizedMap());
try {
return callback();
} finally {
_inSemiGlobalScope = wasInSemiGlobalScope;
for (var name in _variables.removeLast().keys) {
_variableIndices.remove(name);
}

View File

@ -41,31 +41,14 @@ class Parser {
Stylesheet parse() {
var start = _scanner.state;
var children = <Statement>[];
while (true) {
children.addAll(_comments());
if (_scanner.isDone) break;
switch (_scanner.peekChar()) {
case $dollar:
children.add(_variableDeclaration());
break;
case $at:
children.add(_atRule());
break;
case $semicolon:
_scanner.readChar();
break;
default:
children.add(_styleRule());
break;
}
}
var statements = _statements(_topLevelStatement);
_scanner.expectDone();
return new Stylesheet(children, span: _scanner.spanFrom(start));
return new Stylesheet(statements, span: _scanner.spanFrom(start));
}
Statement _topLevelStatement() {
if (_scanner.peekChar() == $at) return _atRule(_topLevelStatement);
return _styleRule();
}
SelectorList parseSelector() {
@ -116,15 +99,15 @@ class Parser {
StyleRule _styleRule() {
var start = _scanner.state;
var selector = _almostAnyValue();
var children = _ruleChildren();
var children = _children(_ruleChild);
return new StyleRule(selector, children,
span: _scanner.spanFrom(start));
}
List<Statement> _ruleChildren() => _children(() {
if (_scanner.peekChar() == $at) return _atRule();
Statement _ruleChild() {
if (_scanner.peekChar() == $at) return _atRule(_ruleChild);
return _declarationOrStyleRule();
});
}
Expression _declarationExpression() {
if (_scanner.peekChar() == $lbrace) {
@ -169,7 +152,7 @@ class Parser {
var buffer = declarationOrBuffer as InterpolationBuffer;
buffer.addInterpolation(_almostAnyValue());
var children = _ruleChildren();
var children = _children(_ruleChild);
return new StyleRule(
buffer.interpolation(_scanner.spanFrom(start)), children,
span: _scanner.spanFrom(start));
@ -222,7 +205,7 @@ class Parser {
var postColonWhitespace = _commentText();
if (_scanner.peekChar() == $lbrace) {
return new Declaration(name,
children: _declarationChildren(),
children: _children(_declarationChild),
span: _scanner.spanFrom(start));
}
@ -262,7 +245,7 @@ class Parser {
name,
value: value,
children: _scanner.peekChar() == $lbrace
? _declarationChildren()
? _children(_declarationChild)
: null,
span: _scanner.spanFrom(start));
}
@ -276,7 +259,7 @@ class Parser {
if (_scanner.peekChar() == $lbrace) {
return new Declaration(name,
children: _declarationChildren(),
children: _children(_declarationChild),
span: _scanner.spanFrom(start));
}
@ -285,15 +268,15 @@ class Parser {
name,
value: value,
children: _scanner.peekChar() == $lbrace
? _declarationChildren()
? _children(_declarationChild)
: null,
span: _scanner.spanFrom(start));
}
List<Statement> _declarationChildren() => _children(() {
Statement _declarationChild() {
if (_scanner.peekChar() == $at) return _declarationAtRule();
return _declaration();
});
}
/// Consumes whitespace if available and returns any comments it contained.
List<Comment> _comments() {
@ -310,7 +293,7 @@ class Parser {
// # At Rules
Statement _atRule() {
Statement _atRule(Statement child()) {
var start = _scanner.state;
var name = _atRuleName();
@ -318,6 +301,7 @@ class Parser {
case "content": return _content(start);
case "extend": return _extend(start);
case "function": return _functionDeclaration(start);
case "if": return _if(start, child);
case "include": return _include(start);
case "media": return _mediaRule(start);
case "mixin": return _mixinDeclaration(start);
@ -331,6 +315,7 @@ class Parser {
switch (name) {
case "content": return _content(start);
case "if": return _if(start, _declarationChild);
case "include": return _include(start);
default: return _disallowedAtRule(start);
}
@ -339,6 +324,7 @@ class Parser {
Statement _functionAtRule() {
var start = _scanner.state;
switch (_atRuleName()) {
case "if": return _if(start, _functionAtRule);
case "return": return _return(start);
default: return _disallowedAtRule(start);
}
@ -386,6 +372,9 @@ class Parser {
span: _scanner.spanFrom(start));
}
If _if(LineScannerState start, Statement child()) =>
new If(_expression(), _children(child), span: _scanner.spanFrom(start));
Include _include(LineScannerState start) {
var name = _identifier();
_ignoreComments();
@ -397,7 +386,7 @@ class Parser {
List<Statement> children;
if (_scanner.peekChar() == $lbrace) {
_inContentBlock = true;
children = _ruleChildren();
children = _children(_ruleChild);
_inContentBlock = false;
}
@ -406,7 +395,7 @@ class Parser {
}
MediaRule _mediaRule(LineScannerState start) =>
new MediaRule(_mediaQueryList(), _ruleChildren(),
new MediaRule(_mediaQueryList(), _children(_ruleChild),
span: _scanner.spanFrom(start));
MixinDeclaration _mixinDeclaration(LineScannerState start) {
@ -425,7 +414,7 @@ class Parser {
_ignoreComments();
_inMixin = true;
_mixinHasContent = false;
var children = _ruleChildren();
var children = _children(_ruleChild);
_inMixin = false;
return new MixinDeclaration(name, arguments, children,
@ -448,7 +437,7 @@ class Parser {
return new AtRule(name,
value: value,
children: _scanner.peekChar() == $lbrace ? _ruleChildren() : null,
children: _scanner.peekChar() == $lbrace ? _children(_ruleChild) : null,
span: _scanner.spanFrom(start));
}
@ -1668,28 +1657,50 @@ class Parser {
character == $dollar || isNameStart(character) || isDigit(character);
}
List<Statement> _children(Statement consumeChild()) {
List<Statement> _children(Statement child()) {
_scanner.expectChar($lbrace);
var children = <Statement>[];
loop: while (true) {
while (true) {
children.addAll(_comments());
switch (_scanner.peekChar()) {
case $dollar:
children.add(_variableDeclaration());
continue loop;
break;
case $semicolon:
_scanner.readChar();
continue loop;
break;
case $rbrace:
break loop;
}
_scanner.expectChar($rbrace);
return children;
children.add(consumeChild());
default:
children.add(child());
break;
}
}
_scanner.expectChar($rbrace);
return children;
}
List<Statement> _statements(Statement statement()) {
var statements = <Statement>[];
while (!_scanner.isDone) {
switch (_scanner.peekChar()) {
case $dollar:
statements.add(_variableDeclaration());
break;
case $semicolon:
_scanner.readChar();
break;
default:
statements.add(statement());
break;
}
statements.addAll(_comments());
}
return statements;
}
String _rawText(void consumer()) {

View File

@ -19,6 +19,8 @@ abstract class Value {
/// Whether the value will be represented in CSS as the empty string.
bool get isBlank => false;
bool get isTruthy => true;
const Value();
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor);

View File

@ -11,6 +11,8 @@ const sassFalse = const SassBoolean._(false);
class SassBoolean extends Value {
final bool value;
bool get isTruthy => value;
factory SassBoolean(bool value) => value ? sassTrue : sassFalse;
const SassBoolean._(this.value);

View File

@ -34,6 +34,13 @@ abstract class StatementVisitor<T> {
return null;
}
T visitIf(If node) {
for (var child in node.children) {
child.accept(this);
}
return null;
}
T visitInclude(Include node) {
if (node.children == null) return null;
for (var child in node.children) {

View File

@ -148,6 +148,12 @@ class PerformVisitor extends StatementVisitor
new UserDefinedCallable(node, _environment.closure()));
}
void visitIf(If node) {
var condition = node.expression.accept(this);
if (!condition.isTruthy) return;
_environment.scope(() => super.visitIf(node), semiGlobal: true);
}
void visitInclude(Include node) {
var mixin = _environment.getMixin(node.name) as UserDefinedCallable;
if (mixin == null) throw node.span.message("Undefined mixin.");