Support @each

This commit is contained in:
Natalie Weizenbaum 2016-09-07 10:18:30 -07:00 committed by Natalie Weizenbaum
parent e873242ac4
commit 1832879845
8 changed files with 102 additions and 3 deletions

View File

@ -28,6 +28,7 @@ export 'sass/statement/comment.dart';
export 'sass/statement/content_rule.dart';
export 'sass/statement/debug_rule.dart';
export 'sass/statement/declaration.dart';
export 'sass/statement/each_rule.dart';
export 'sass/statement/error_rule.dart';
export 'sass/statement/extend_rule.dart';
export 'sass/statement/for_rule.dart';

View File

@ -0,0 +1,31 @@
// 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 EachRule implements Statement {
final List<String> variables;
final Expression list;
final List<Statement> children;
final FileSpan span;
EachRule(Iterable<String> variables, this.list, Iterable<Statement> children,
this.span)
: variables = new List.unmodifiable(variables),
children = new List.unmodifiable(children);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitEachRule(this);
String toString() =>
"@each ${variables.map((variable) => '\$' + variable).join(', ')} in "
"$list {${children.join(" ")}}";
}

View File

@ -327,6 +327,8 @@ class Parser {
return _contentRule(start);
case "debug":
return _debugRule(start);
case "each":
return _eachRule(start, child);
case "error":
return _errorRule(start);
case "extend":
@ -367,6 +369,8 @@ class Parser {
return _contentRule(start);
case "debug":
return _debugRule(start);
case "each":
return _eachRule(start, _declarationChild);
case "error":
return _errorRule(start);
case "for":
@ -389,6 +393,8 @@ class Parser {
switch (_atRuleName()) {
case "debug":
return _debugRule(start);
case "each":
return _eachRule(start, _functionAtRule);
case "error":
return _errorRule(start);
case "for":
@ -436,6 +442,28 @@ class Parser {
DebugRule _debugRule(LineScannerState start) =>
new DebugRule(_expression(), _scanner.spanFrom(start));
EachRule _eachRule(LineScannerState start, Statement child()) {
var wasInControlDirective = _inControlDirective;
_inControlDirective = true;
var variables = [_variableName()];
_ignoreComments();
while (_scanner.scanChar($comma)) {
_ignoreComments();
variables.add(_variableName());
_ignoreComments();
}
_scanner.expect("in");
_ignoreComments();
var list = _expression();
var children = _children(child);
_inControlDirective = wasInControlDirective;
return new EachRule(variables, list, children, _scanner.spanFrom(start));
}
ErrorRule _errorRule(LineScannerState start) =>
new ErrorRule(_expression(), _scanner.spanFrom(start));
@ -1938,6 +1966,7 @@ class Parser {
/// [the CSS algorithm]: https://drafts.csswg.org/css-syntax-3/#would-start-an-identifier
bool _lookingAtInterpolatedIdentifier() {
var first = _scanner.peekChar();
if (first == null) return false;
if (isNameStart(first) || first == $backslash) return true;
if (first == $hash) return _scanner.peekChar(1) == $lbrace;

View File

@ -24,12 +24,12 @@ abstract class Value {
int get asInt => throw new InternalException("$this is not an int.");
List<Value> get asList => [this];
const Value();
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor);
List<Value> asList() => [this];
Value unaryPlus() => new SassIdentifier("+${valueToCss(this)}");
Value unaryMinus() => new SassIdentifier("-${valueToCss(this)}");

View File

@ -15,6 +15,8 @@ class SassList extends Value {
bool get isBlank => contents.every((element) => element.isBlank);
List<Value> get asList => contents;
SassList(Iterable<Value> contents, this.separator, {bool bracketed: false})
: contents = new List.unmodifiable(contents),
isBracketed = bracketed;

View File

@ -10,6 +10,14 @@ import '../value.dart';
class SassMap extends Value {
final Map<Value, Value> contents;
List<SassList> get asList {
var result = <SassList>[];
contents.forEach((key, value) {
result.add(new SassList([key, value], ListSeparator.space));
});
return result;
}
SassMap(Map<Value, Value> contents)
: contents = new Map.unmodifiable(contents);

View File

@ -11,6 +11,7 @@ abstract class StatementVisitor<T> {
T visitContentRule(ContentRule node);
T visitDebugRule(DebugRule node);
T visitDeclaration(Declaration node);
T visitEachRule(EachRule node);
T visitErrorRule(ErrorRule node);
T visitExtendRule(ExtendRule node);
T visitForRule(ForRule node);

View File

@ -211,6 +211,33 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
}
}
void visitEachRule(EachRule node) {
var list = node.list.accept(this);
var setVariables = node.variables.length == 1
? (value) => _environment.setLocalVariable(node.variables.first, value)
: (value) => _setMultipleVariables(node.variables, value);
_environment.scope(() {
for (var element in list.asList) {
setVariables(element);
for (var child in node.children) {
child.accept(this);
}
}
}, semiGlobal: true);
}
void _setMultipleVariables(List<String> variables, Value value) {
var list = value.asList;
var minLength = math.min(variables.length, list.length);
for (var i = 0; i < minLength; i++) {
_environment.setLocalVariable(variables[i], list[i]);
}
for (var i = minLength; i < variables.length; i++) {
// TODO: Sass null
_environment.setLocalVariable(variables[i], null);
}
}
void visitErrorRule(ErrorRule node) {
throw _exception(node.expression.accept(this).toString(), node.span);
}
@ -720,7 +747,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
if (rest is SassMap) {
_addRestMap(named, rest, invocation.span);
} else if (rest is SassList) {
positional.addAll(rest.asList());
positional.addAll(rest.asList);
} else {
positional.add(rest);
}