mirror of
https://github.com/danog/dart-sass.git
synced 2025-01-22 05:41:14 +01:00
Add @if.
This commit is contained in:
parent
c4cda30bc0
commit
82aca21682
@ -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';
|
||||
|
25
lib/src/ast/sass/statement/if.dart
Normal file
25
lib/src/ast/sass/statement/if.dart
Normal 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(" ")}}";
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
children.add(consumeChild());
|
||||
}
|
||||
_scanner.expectChar($rbrace);
|
||||
return children;
|
||||
|
||||
default:
|
||||
children.add(child());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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.");
|
||||
|
Loading…
x
Reference in New Issue
Block a user