@content support.

This commit is contained in:
Natalie Weizenbaum 2016-08-27 17:43:15 -07:00 committed by Natalie Weizenbaum
parent 3b3e3046fd
commit 0573fa7709
7 changed files with 99 additions and 11 deletions

View File

@ -24,6 +24,7 @@ export 'sass/node.dart';
export 'sass/statement.dart';
export 'sass/statement/at_rule.dart';
export 'sass/statement/comment.dart';
export 'sass/statement/content.dart';
export 'sass/statement/declaration.dart';
export 'sass/statement/extend_rule.dart';
export 'sass/statement/function_declaration.dart';

View File

@ -0,0 +1,19 @@
// 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 '../statement.dart';
class Content implements Statement {
final FileSpan span;
Content({this.span});
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitContent(this);
String toString() => "@content;";
}

View File

@ -10,8 +10,10 @@ import '../argument_declaration.dart';
import '../statement.dart';
class MixinDeclaration extends CallableDeclaration {
final bool hasContent;
MixinDeclaration(String name, ArgumentDeclaration arguments,
Iterable<Statement> children, {FileSpan span})
Iterable<Statement> children, {this.hasContent: false, FileSpan span})
: super(name, arguments, children, span: span);
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>

View File

@ -2,6 +2,7 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'ast/sass.dart';
import 'callable.dart';
import 'functions.dart';
import 'value.dart';
@ -24,6 +25,14 @@ class Environment {
// Note: this is not necessarily complete
final Map<String, int> _mixinIndices;
/// The content block passed to the lexically-current mixin, if any.
List<Statement> get contentBlock => _contentBlock;
List<Statement> _contentBlock;
/// The environment for [_contentBlock].
Environment get contentEnvironment => _contentEnvironment;
Environment _contentEnvironment;
Environment()
: _variables = [normalizedMap()],
_variableIndices = normalizedMap(),
@ -35,7 +44,8 @@ class Environment {
}
Environment._(this._variables, this._variableIndices, this._functions,
this._functionIndices, this._mixins, this._mixinIndices);
this._functionIndices, this._mixins, this._mixinIndices,
this._contentBlock, this._contentEnvironment);
Environment closure() => new Environment._(
_variables.toList(),
@ -43,7 +53,9 @@ class Environment {
_functions.toList(),
new Map.from(_functionIndices),
_mixins.toList(),
new Map.from(_mixinIndices));
new Map.from(_mixinIndices),
_contentBlock,
_contentEnvironment);
Value getVariable(String name) =>
_variables[_variableIndices[name] ?? 0][name];
@ -99,6 +111,17 @@ class Environment {
_mixins[_mixins.length - 1][callable.name] = callable;
}
void withContent(List<Statement> block, Environment environment,
void callback()) {
var oldBlock = _contentBlock;
var oldEnvironment = _contentEnvironment;
_contentBlock = block;
_contentEnvironment = environment;
callback();
_contentBlock = oldBlock;
_contentEnvironment = oldEnvironment;
}
/*=T*/ scope/*<T>*/(/*=T*/ callback()) {
// TODO: avoid creating a new scope if no variables are declared.
_variables.add(normalizedMap());

View File

@ -23,7 +23,11 @@ final _prefixedSelectorPseudoClasses =
class Parser {
final SpanScanner _scanner;
bool _inMixin = false;
var _inMixin = false;
var _inContentBlock = false;
bool _mixinHasContent;
Parser(String contents, {url})
: _scanner = new SpanScanner(contents, sourceUrl: url);
@ -116,6 +120,7 @@ class Parser {
_ignoreComments();
switch (name) {
case "content": return _content(start);
case "extend":
return new ExtendRule(_almostAnyValue(),
span: _scanner.spanFrom(start));
@ -140,6 +145,19 @@ class Parser {
span: _scanner.spanFrom(start));
}
Content _content(LineScannerState start) {
if (_inMixin) {
_mixinHasContent = true;
return new Content(span: _scanner.spanFrom(start));
}
_scanner.error(
"@content is only allowed within mixin declarations.",
position: start.position,
length: "@content".length);
return null;
}
Include _include(LineScannerState start) {
var name = _identifier();
_ignoreComments();
@ -150,9 +168,9 @@ class Parser {
List<Statement> children;
if (_scanner.peekChar() == $lbrace) {
_inMixin = true;
_inContentBlock = true;
children = _ruleChildren();
_inMixin = false;
_inContentBlock = false;
}
return new Include(name, arguments,
@ -166,7 +184,7 @@ class Parser {
? _argumentDeclaration()
: new ArgumentDeclaration.empty(span: _scanner.emptySpan);
if (_inMixin) {
if (_inMixin || _inContentBlock) {
throw new StringScannerException(
"Mixins may not contain mixin declarations.",
_scanner.spanFrom(start), _scanner.string);
@ -174,10 +192,12 @@ class Parser {
_ignoreComments();
_inMixin = true;
_mixinHasContent = false;
var children = _ruleChildren();
_inMixin = false;
return new MixinDeclaration(name, arguments, children,
hasContent: _mixinHasContent,
span: _scanner.spanFrom(start));
}
@ -186,7 +206,7 @@ class Parser {
_ignoreComments();
var arguments = _argumentDeclaration();
if (_inMixin) {
if (_inMixin || _inContentBlock) {
throw new StringScannerException(
"Mixins may not contain function declarations.",
_scanner.spanFrom(start), _scanner.string);

View File

@ -6,6 +6,7 @@ import '../../ast/sass.dart';
abstract class StatementVisitor<T> {
T visitComment(Comment node) => null;
T visitContent(Content node) => null;
T visitExtendRule(ExtendRule node) => null;
T visitReturn(Return node) => null;
T visitVariableDeclaration(VariableDeclaration node) => null;

View File

@ -64,6 +64,17 @@ class PerformVisitor extends StatementVisitor
_parent.addChild(new CssComment(node.text, span: node.span));
}
void visitContent(Content node) {
var block = _environment.contentBlock;
if (block == null) return;
_withEnvironment(_environment.contentEnvironment, () {
for (var statement in block) {
statement.accept(this);
}
});
}
void visitDeclaration(Declaration node) {
var name = _interpolationToValue(node.name);
if (_declarationName != null) {
@ -131,16 +142,27 @@ class PerformVisitor extends StatementVisitor
void visitInclude(Include node) {
var mixin = _environment.getMixin(node.name) as UserDefinedCallable;
if (mixin == null) throw node.span.message("Undefined mixin.");
if (node.children != null) {
if (node.children != null &&
!(mixin.declaration as MixinDeclaration).hasContent) {
throw node.span.message("Mixin doesn't accept a content block.");
}
_runUserDefinedCallable(node, mixin, () {
Value callback() {
for (var statement in mixin.declaration.children) {
statement.accept(this);
}
return null;
});
}
if (node.children == null) {
_runUserDefinedCallable(node, mixin, callback);
} else {
var environment = _environment.closure();
_runUserDefinedCallable(node, mixin, () {
_environment.withContent(node.children, environment, callback);
});
}
}
void visitMixinDeclaration(MixinDeclaration node) {