diff --git a/lib/src/ast/sass.dart b/lib/src/ast/sass.dart index d207777e..f5f9140a 100644 --- a/lib/src/ast/sass.dart +++ b/lib/src/ast/sass.dart @@ -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'; diff --git a/lib/src/ast/sass/statement/each_rule.dart b/lib/src/ast/sass/statement/each_rule.dart new file mode 100644 index 00000000..9b1dbf1f --- /dev/null +++ b/lib/src/ast/sass/statement/each_rule.dart @@ -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 variables; + + final Expression list; + + final List children; + + final FileSpan span; + + EachRule(Iterable variables, this.list, Iterable children, + this.span) + : variables = new List.unmodifiable(variables), + children = new List.unmodifiable(children); + + /*=T*/ accept/**/(StatementVisitor/**/ visitor) => + visitor.visitEachRule(this); + + String toString() => + "@each ${variables.map((variable) => '\$' + variable).join(', ')} in " + "$list {${children.join(" ")}}"; +} diff --git a/lib/src/parser.dart b/lib/src/parser.dart index e3eead4a..9bf998b3 100644 --- a/lib/src/parser.dart +++ b/lib/src/parser.dart @@ -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; diff --git a/lib/src/value.dart b/lib/src/value.dart index 6eb9629b..ee19b1a2 100644 --- a/lib/src/value.dart +++ b/lib/src/value.dart @@ -24,12 +24,12 @@ abstract class Value { int get asInt => throw new InternalException("$this is not an int."); + List get asList => [this]; + const Value(); /*=T*/ accept/**/(ValueVisitor/**/ visitor); - List asList() => [this]; - Value unaryPlus() => new SassIdentifier("+${valueToCss(this)}"); Value unaryMinus() => new SassIdentifier("-${valueToCss(this)}"); diff --git a/lib/src/value/list.dart b/lib/src/value/list.dart index 24ee873d..0ed3cec1 100644 --- a/lib/src/value/list.dart +++ b/lib/src/value/list.dart @@ -15,6 +15,8 @@ class SassList extends Value { bool get isBlank => contents.every((element) => element.isBlank); + List get asList => contents; + SassList(Iterable contents, this.separator, {bool bracketed: false}) : contents = new List.unmodifiable(contents), isBracketed = bracketed; diff --git a/lib/src/value/map.dart b/lib/src/value/map.dart index 04f8efa7..b4855ac3 100644 --- a/lib/src/value/map.dart +++ b/lib/src/value/map.dart @@ -10,6 +10,14 @@ import '../value.dart'; class SassMap extends Value { final Map contents; + List get asList { + var result = []; + contents.forEach((key, value) { + result.add(new SassList([key, value], ListSeparator.space)); + }); + return result; + } + SassMap(Map contents) : contents = new Map.unmodifiable(contents); diff --git a/lib/src/visitor/interface/statement.dart b/lib/src/visitor/interface/statement.dart index 7b21292e..68b0ff40 100644 --- a/lib/src/visitor/interface/statement.dart +++ b/lib/src/visitor/interface/statement.dart @@ -11,6 +11,7 @@ abstract class StatementVisitor { 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); diff --git a/lib/src/visitor/perform.dart b/lib/src/visitor/perform.dart index 446e89be..3423ad13 100644 --- a/lib/src/visitor/perform.dart +++ b/lib/src/visitor/perform.dart @@ -211,6 +211,33 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor { } } + 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 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 { 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); }