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/declaration.dart';
|
||||||
export 'sass/statement/error_rule.dart';
|
export 'sass/statement/error_rule.dart';
|
||||||
export 'sass/statement/extend_rule.dart';
|
export 'sass/statement/extend_rule.dart';
|
||||||
|
export 'sass/statement/for_rule.dart';
|
||||||
export 'sass/statement/function_rule.dart';
|
export 'sass/statement/function_rule.dart';
|
||||||
export 'sass/statement/if_rule.dart';
|
export 'sass/statement/if_rule.dart';
|
||||||
export 'sass/statement/import_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.
|
/// Base is global scope.
|
||||||
final List<Map<String, Value>> _variables;
|
final List<Map<String, Value>> _variables;
|
||||||
|
|
||||||
|
// Note: this is not necessarily complete
|
||||||
final Map<String, int> _variableIndices;
|
final Map<String, int> _variableIndices;
|
||||||
|
|
||||||
final List<Map<String, Callable>> _functions;
|
final List<Map<String, Callable>> _functions;
|
||||||
@ -68,8 +69,23 @@ class Environment {
|
|||||||
Environment global() => new Environment._([_variables.first], {},
|
Environment global() => new Environment._([_variables.first], {},
|
||||||
[_functions.first], {}, [_mixins.first], {}, null, null);
|
[_functions.first], {}, [_mixins.first], {}, null, null);
|
||||||
|
|
||||||
Value getVariable(String name) =>
|
Value getVariable(String name) {
|
||||||
_variables[_variableIndices[name] ?? 0][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}) {
|
void setVariable(String name, Value value, {bool global: false}) {
|
||||||
var index = global || _variables.length == 1
|
var index = global || _variables.length == 1
|
||||||
@ -79,6 +95,12 @@ class Environment {
|
|||||||
_variables[index][name] = value;
|
_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) {
|
Callable getFunction(String name) {
|
||||||
var index = _functionIndices[name];
|
var index = _functionIndices[name];
|
||||||
if (index != null) _functions[index][name];
|
if (index != null) _functions[index][name];
|
||||||
@ -135,7 +157,7 @@ class Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*=T*/ scope/*<T>*/(/*=T*/ callback(), {bool semiGlobal: false}) {
|
/*=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.
|
// TODO: avoid creating a new scope if no variables are declared.
|
||||||
var wasInSemiGlobalScope = _inSemiGlobalScope;
|
var wasInSemiGlobalScope = _inSemiGlobalScope;
|
||||||
|
@ -94,10 +94,8 @@ class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VariableDeclaration _variableDeclaration() {
|
VariableDeclaration _variableDeclaration() {
|
||||||
if (!_scanner.scanChar($dollar)) return null;
|
|
||||||
|
|
||||||
var start = _scanner.state;
|
var start = _scanner.state;
|
||||||
var name = _identifier();
|
var name = _variableName();
|
||||||
_ignoreComments();
|
_ignoreComments();
|
||||||
_scanner.expectChar($colon);
|
_scanner.expectChar($colon);
|
||||||
_ignoreComments();
|
_ignoreComments();
|
||||||
@ -333,6 +331,8 @@ class Parser {
|
|||||||
return _errorRule(start);
|
return _errorRule(start);
|
||||||
case "extend":
|
case "extend":
|
||||||
return _extendRule(start);
|
return _extendRule(start);
|
||||||
|
case "for":
|
||||||
|
return _forRule(start, child);
|
||||||
case "function":
|
case "function":
|
||||||
return _functionRule(start);
|
return _functionRule(start);
|
||||||
case "if":
|
case "if":
|
||||||
@ -367,6 +367,8 @@ class Parser {
|
|||||||
return _debugRule(start);
|
return _debugRule(start);
|
||||||
case "error":
|
case "error":
|
||||||
return _errorRule(start);
|
return _errorRule(start);
|
||||||
|
case "for":
|
||||||
|
return _forRule(start, _declarationAtRule);
|
||||||
case "if":
|
case "if":
|
||||||
return _ifRule(start, _declarationChild);
|
return _ifRule(start, _declarationChild);
|
||||||
case "include":
|
case "include":
|
||||||
@ -385,6 +387,8 @@ class Parser {
|
|||||||
return _debugRule(start);
|
return _debugRule(start);
|
||||||
case "error":
|
case "error":
|
||||||
return _errorRule(start);
|
return _errorRule(start);
|
||||||
|
case "for":
|
||||||
|
return _forRule(start, _functionAtRule);
|
||||||
case "if":
|
case "if":
|
||||||
return _ifRule(start, _functionAtRule);
|
return _ifRule(start, _functionAtRule);
|
||||||
case "return":
|
case "return":
|
||||||
@ -456,6 +460,32 @@ class Parser {
|
|||||||
name, arguments, children, _scanner.spanFrom(start));
|
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()) {
|
IfRule _ifRule(LineScannerState start, Statement child()) {
|
||||||
var wasInControlDirective = _inControlDirective;
|
var wasInControlDirective = _inControlDirective;
|
||||||
_inControlDirective = true;
|
_inControlDirective = true;
|
||||||
@ -676,6 +706,31 @@ class Parser {
|
|||||||
return new ListExpression(commaExpressions, ListSeparator.comma);
|
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() {
|
ListExpression _bracketedList() {
|
||||||
var start = _scanner.state;
|
var start = _scanner.state;
|
||||||
_scanner.expectChar($lbracket);
|
_scanner.expectChar($lbracket);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// MIT-style license that can be found in the LICENSE file or at
|
// MIT-style license that can be found in the LICENSE file or at
|
||||||
// https://opensource.org/licenses/MIT.
|
// https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
|
import 'exception.dart';
|
||||||
import 'value/boolean.dart';
|
import 'value/boolean.dart';
|
||||||
import 'value/identifier.dart';
|
import 'value/identifier.dart';
|
||||||
import 'visitor/interface/value.dart';
|
import 'visitor/interface/value.dart';
|
||||||
@ -21,6 +22,8 @@ abstract class Value {
|
|||||||
|
|
||||||
bool get isTruthy => true;
|
bool get isTruthy => true;
|
||||||
|
|
||||||
|
int get asInt => throw new InternalException("$this is not an int.");
|
||||||
|
|
||||||
const Value();
|
const Value();
|
||||||
|
|
||||||
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor);
|
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// MIT-style license that can be found in the LICENSE file or at
|
// MIT-style license that can be found in the LICENSE file or at
|
||||||
// https://opensource.org/licenses/MIT.
|
// https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
|
import '../exception.dart';
|
||||||
|
import '../utils.dart';
|
||||||
import '../visitor/interface/value.dart';
|
import '../visitor/interface/value.dart';
|
||||||
import '../value.dart';
|
import '../value.dart';
|
||||||
|
|
||||||
@ -10,6 +12,13 @@ class SassNumber extends Value {
|
|||||||
|
|
||||||
final num 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);
|
SassNumber(this.value);
|
||||||
|
|
||||||
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor) =>
|
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor) =>
|
||||||
|
@ -13,6 +13,7 @@ abstract class StatementVisitor<T> {
|
|||||||
T visitDeclaration(Declaration node);
|
T visitDeclaration(Declaration node);
|
||||||
T visitErrorRule(ErrorRule node);
|
T visitErrorRule(ErrorRule node);
|
||||||
T visitExtendRule(ExtendRule node);
|
T visitExtendRule(ExtendRule node);
|
||||||
|
T visitForRule(ForRule node);
|
||||||
T visitFunctionRule(FunctionRule node);
|
T visitFunctionRule(FunctionRule node);
|
||||||
T visitIfRule(IfRule node);
|
T visitIfRule(IfRule node);
|
||||||
T visitImportRule(ImportRule node);
|
T visitImportRule(ImportRule node);
|
||||||
|
@ -264,6 +264,26 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
|||||||
}, through: (node) => node is CssStyleRule);
|
}, 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) {
|
void visitFunctionRule(FunctionRule node) {
|
||||||
_environment
|
_environment
|
||||||
.setFunction(new UserDefinedCallable(node, _environment.closure()));
|
.setFunction(new UserDefinedCallable(node, _environment.closure()));
|
||||||
@ -427,12 +447,9 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
|||||||
|
|
||||||
var selectorText = _interpolationToValue(node.selector, trim: true);
|
var selectorText = _interpolationToValue(node.selector, trim: true);
|
||||||
var parsedSelector = new Parser(selectorText.value).parseSelector();
|
var parsedSelector = new Parser(selectorText.value).parseSelector();
|
||||||
|
parsedSelector = _addExceptionSpan(
|
||||||
try {
|
() => parsedSelector.resolveParentSelectors(_selector?.value),
|
||||||
parsedSelector = parsedSelector.resolveParentSelectors(_selector?.value);
|
node.selector.span);
|
||||||
} on InternalException catch (error) {
|
|
||||||
throw _exception(error.message, node.selector.span);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: catch errors and re-contextualize them relative to
|
// TODO: catch errors and re-contextualize them relative to
|
||||||
// [node.selector.span.start].
|
// [node.selector.span.start].
|
||||||
@ -860,4 +877,12 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
|
|||||||
|
|
||||||
SassRuntimeException _exception(String message, FileSpan span) =>
|
SassRuntimeException _exception(String message, FileSpan span) =>
|
||||||
new SassRuntimeException(message, span, _stackTrace(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