mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-22 13:51:31 +01:00
Support @for.
This commit is contained in:
parent
202fcad40f
commit
80f0afb484
@ -30,6 +30,7 @@ export 'sass/statement/debug_rule.dart';
|
||||
export 'sass/statement/declaration.dart';
|
||||
export 'sass/statement/error_rule.dart';
|
||||
export 'sass/statement/extend_rule.dart';
|
||||
export 'sass/statement/for_rule.dart';
|
||||
export 'sass/statement/function_rule.dart';
|
||||
export 'sass/statement/if_rule.dart';
|
||||
export 'sass/statement/import_rule.dart';
|
||||
|
36
lib/src/ast/sass/statement/for_rule.dart
Normal file
36
lib/src/ast/sass/statement/for_rule.dart
Normal file
@ -0,0 +1,36 @@
|
||||
// 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 ForRule implements Statement {
|
||||
final String variable;
|
||||
|
||||
final Expression from;
|
||||
|
||||
final Expression to;
|
||||
|
||||
final bool isExclusive;
|
||||
|
||||
final List<Statement> children;
|
||||
|
||||
final FileSpan span;
|
||||
|
||||
ForRule(this.variable, this.from, this.to, Iterable<Statement> children,
|
||||
this.span,
|
||||
{bool exclusive: true})
|
||||
: children = new List.unmodifiable(children),
|
||||
isExclusive = exclusive;
|
||||
|
||||
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
|
||||
visitor.visitForRule(this);
|
||||
|
||||
String toString() =>
|
||||
"@for \$$variable from $from ${isExclusive ? 'to' : 'through'} $to "
|
||||
"{${children.join(" ")}}";
|
||||
}
|
@ -13,6 +13,7 @@ class Environment {
|
||||
/// Base is global scope.
|
||||
final List<Map<String, Value>> _variables;
|
||||
|
||||
// Note: this is not necessarily complete
|
||||
final Map<String, int> _variableIndices;
|
||||
|
||||
final List<Map<String, Callable>> _functions;
|
||||
@ -68,8 +69,23 @@ class Environment {
|
||||
Environment global() => new Environment._([_variables.first], {},
|
||||
[_functions.first], {}, [_mixins.first], {}, null, null);
|
||||
|
||||
Value getVariable(String name) =>
|
||||
_variables[_variableIndices[name] ?? 0][name];
|
||||
Value getVariable(String name) {
|
||||
var index = _variableIndices[name];
|
||||
if (index != null) _variables[index][name];
|
||||
|
||||
index = _variableIndex(name);
|
||||
if (index == null) return null;
|
||||
|
||||
_variableIndices[name] = index;
|
||||
return _variables[index][name];
|
||||
}
|
||||
|
||||
int _variableIndex(String name) {
|
||||
for (var i = _variables.length - 1; i >= 0; i--) {
|
||||
if (_variables[i].containsKey(name)) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void setVariable(String name, Value value, {bool global: false}) {
|
||||
var index = global || _variables.length == 1
|
||||
@ -79,6 +95,12 @@ class Environment {
|
||||
_variables[index][name] = value;
|
||||
}
|
||||
|
||||
void setLocalVariable(String name, Value value) {
|
||||
var index = _variables.length - 1;
|
||||
_variableIndices[name] = index;
|
||||
_variables[index][name] = value;
|
||||
}
|
||||
|
||||
Callable getFunction(String name) {
|
||||
var index = _functionIndices[name];
|
||||
if (index != null) _functions[index][name];
|
||||
@ -135,7 +157,7 @@ class Environment {
|
||||
}
|
||||
|
||||
/*=T*/ scope/*<T>*/(/*=T*/ callback(), {bool semiGlobal: false}) {
|
||||
assert(!semiGlobal || _inSemiGlobalScope || _variables.length == 1);
|
||||
semiGlobal = semiGlobal && (_inSemiGlobalScope || _variables.length == 1);
|
||||
|
||||
// TODO: avoid creating a new scope if no variables are declared.
|
||||
var wasInSemiGlobalScope = _inSemiGlobalScope;
|
||||
|
@ -94,10 +94,8 @@ class Parser {
|
||||
}
|
||||
|
||||
VariableDeclaration _variableDeclaration() {
|
||||
if (!_scanner.scanChar($dollar)) return null;
|
||||
|
||||
var start = _scanner.state;
|
||||
var name = _identifier();
|
||||
var name = _variableName();
|
||||
_ignoreComments();
|
||||
_scanner.expectChar($colon);
|
||||
_ignoreComments();
|
||||
@ -333,6 +331,8 @@ class Parser {
|
||||
return _errorRule(start);
|
||||
case "extend":
|
||||
return _extendRule(start);
|
||||
case "for":
|
||||
return _forRule(start, child);
|
||||
case "function":
|
||||
return _functionRule(start);
|
||||
case "if":
|
||||
@ -367,6 +367,8 @@ class Parser {
|
||||
return _debugRule(start);
|
||||
case "error":
|
||||
return _errorRule(start);
|
||||
case "for":
|
||||
return _forRule(start, _declarationAtRule);
|
||||
case "if":
|
||||
return _ifRule(start, _declarationChild);
|
||||
case "include":
|
||||
@ -385,6 +387,8 @@ class Parser {
|
||||
return _debugRule(start);
|
||||
case "error":
|
||||
return _errorRule(start);
|
||||
case "for":
|
||||
return _forRule(start, _functionAtRule);
|
||||
case "if":
|
||||
return _ifRule(start, _functionAtRule);
|
||||
case "return":
|
||||
@ -456,6 +460,32 @@ class Parser {
|
||||
name, arguments, children, _scanner.spanFrom(start));
|
||||
}
|
||||
|
||||
ForRule _forRule(LineScannerState start, Statement child()) {
|
||||
var wasInControlDirective = _inControlDirective;
|
||||
_inControlDirective = true;
|
||||
var variable = _variableName();
|
||||
_ignoreComments();
|
||||
|
||||
_scanner.expect("from");
|
||||
_ignoreComments();
|
||||
var from = _expressionUntil(() {
|
||||
if (!_scanner.matches("to") && !_scanner.matches("through")) return false;
|
||||
var after = _scanner.peekChar(_scanner.lastMatch[0].length);
|
||||
return after == null || isWhitespace(after);
|
||||
});
|
||||
|
||||
var exclusive = _scanner.scan("to");
|
||||
if (!exclusive) _scanner.expect("through", name: '"to" or "through"');
|
||||
_ignoreComments();
|
||||
var to = _expression();
|
||||
|
||||
var children = _children(child);
|
||||
_inControlDirective = wasInControlDirective;
|
||||
|
||||
return new ForRule(variable, from, to, children, _scanner.spanFrom(start),
|
||||
exclusive: exclusive);
|
||||
}
|
||||
|
||||
IfRule _ifRule(LineScannerState start, Statement child()) {
|
||||
var wasInControlDirective = _inControlDirective;
|
||||
_inControlDirective = true;
|
||||
@ -676,6 +706,31 @@ class Parser {
|
||||
return new ListExpression(commaExpressions, ListSeparator.comma);
|
||||
}
|
||||
|
||||
Expression _expressionUntil(bool isDone()) {
|
||||
if (isDone()) _scanner.error("Expected expression.");
|
||||
var first = _singleExpression();
|
||||
_ignoreComments();
|
||||
if (!isDone() && _lookingAtExpression()) {
|
||||
var spaceExpressions = [first];
|
||||
do {
|
||||
spaceExpressions.add(_singleExpression());
|
||||
_ignoreComments();
|
||||
} while (!isDone() && _lookingAtExpression());
|
||||
first = new ListExpression(spaceExpressions, ListSeparator.space);
|
||||
}
|
||||
|
||||
if (!_scanner.scanChar($comma)) return first;
|
||||
|
||||
var commaExpressions = [first];
|
||||
do {
|
||||
_ignoreComments();
|
||||
if (isDone() || !_lookingAtExpression()) break;
|
||||
commaExpressions.add(_spaceListOrValue());
|
||||
} while (_scanner.scanChar($comma));
|
||||
|
||||
return new ListExpression(commaExpressions, ListSeparator.comma);
|
||||
}
|
||||
|
||||
ListExpression _bracketedList() {
|
||||
var start = _scanner.state;
|
||||
_scanner.expectChar($lbracket);
|
||||
|
@ -2,6 +2,7 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import 'exception.dart';
|
||||
import 'value/boolean.dart';
|
||||
import 'value/identifier.dart';
|
||||
import 'visitor/interface/value.dart';
|
||||
@ -21,6 +22,8 @@ abstract class Value {
|
||||
|
||||
bool get isTruthy => true;
|
||||
|
||||
int get asInt => throw new InternalException("$this is not an int.");
|
||||
|
||||
const Value();
|
||||
|
||||
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor);
|
||||
|
@ -2,6 +2,8 @@
|
||||
// MIT-style license that can be found in the LICENSE file or at
|
||||
// https://opensource.org/licenses/MIT.
|
||||
|
||||
import '../exception.dart';
|
||||
import '../utils.dart';
|
||||
import '../visitor/interface/value.dart';
|
||||
import '../value.dart';
|
||||
|
||||
@ -10,6 +12,13 @@ class SassNumber extends Value {
|
||||
|
||||
final num value;
|
||||
|
||||
bool get isInt => value is int || almostEquals(value % 1, 0.0);
|
||||
|
||||
int get asInt {
|
||||
if (!isInt) throw new InternalException("$this is not an int.");
|
||||
return value.round();
|
||||
}
|
||||
|
||||
SassNumber(this.value);
|
||||
|
||||
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor) =>
|
||||
|
@ -13,6 +13,7 @@ abstract class StatementVisitor<T> {
|
||||
T visitDeclaration(Declaration node);
|
||||
T visitErrorRule(ErrorRule node);
|
||||
T visitExtendRule(ExtendRule node);
|
||||
T visitForRule(ForRule node);
|
||||
T visitFunctionRule(FunctionRule node);
|
||||
T visitIfRule(IfRule node);
|
||||
T visitImportRule(ImportRule node);
|
||||
|
@ -264,6 +264,26 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
}, through: (node) => node is CssStyleRule);
|
||||
}
|
||||
|
||||
void visitForRule(ForRule node) {
|
||||
var from =
|
||||
_addExceptionSpan(() => node.from.accept(this).asInt, node.from.span);
|
||||
var to = _addExceptionSpan(() => node.to.accept(this).asInt, node.to.span);
|
||||
|
||||
// TODO: coerce units
|
||||
var direction = from > to ? -1 : 1;
|
||||
if (!node.isExclusive) to += direction;
|
||||
if (from == to) return;
|
||||
|
||||
_environment.scope(() {
|
||||
for (var i = from; i != to; i += direction) {
|
||||
_environment.setLocalVariable(node.variable, new SassNumber(i));
|
||||
for (var child in node.children) {
|
||||
child.accept(this);
|
||||
}
|
||||
}
|
||||
}, semiGlobal: true);
|
||||
}
|
||||
|
||||
void visitFunctionRule(FunctionRule node) {
|
||||
_environment
|
||||
.setFunction(new UserDefinedCallable(node, _environment.closure()));
|
||||
@ -427,12 +447,9 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
|
||||
var selectorText = _interpolationToValue(node.selector, trim: true);
|
||||
var parsedSelector = new Parser(selectorText.value).parseSelector();
|
||||
|
||||
try {
|
||||
parsedSelector = parsedSelector.resolveParentSelectors(_selector?.value);
|
||||
} on InternalException catch (error) {
|
||||
throw _exception(error.message, node.selector.span);
|
||||
}
|
||||
parsedSelector = _addExceptionSpan(
|
||||
() => parsedSelector.resolveParentSelectors(_selector?.value),
|
||||
node.selector.span);
|
||||
|
||||
// TODO: catch errors and re-contextualize them relative to
|
||||
// [node.selector.span.start].
|
||||
@ -860,4 +877,12 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
||||
|
||||
SassRuntimeException _exception(String message, FileSpan span) =>
|
||||
new SassRuntimeException(message, span, _stackTrace(span));
|
||||
|
||||
/*=T*/ _addExceptionSpan/*<T>*/(/*=T*/ callback(), FileSpan span) {
|
||||
try {
|
||||
return callback();
|
||||
} on InternalException catch (error) {
|
||||
throw _exception(error.message, span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user