Make CssNodes directly mutable.

This shaves off a significant amount of time, since now we don't have to
do what is essentially a full copy of the CSS tree. We may want to use
interfaces to provide an immutable view of the CSS tree for use outside
of the perform visitor.
This commit is contained in:
Natalie Weizenbaum 2016-07-15 15:18:57 -07:00
parent a4a7ec1bd9
commit e8756eb02d
17 changed files with 91 additions and 178 deletions

View File

@ -5,29 +5,21 @@
import 'package:source_span/source_span.dart';
import '../../visitor/css.dart';
import '../parent.dart';
import 'node.dart';
import 'value.dart';
class CssAtRule implements CssNode, Parent<CssNode, CssAtRule> {
class CssAtRule extends CssParentNode {
final String name;
final CssValue<String> value;
final List<CssNode> children;
final FileSpan span;
// TODO: validate that children contains only at-rule and declaration nodes?
CssAtRule(this.name, {this.value, Iterable<CssNode> children, this.span})
: children = children == null ? null : new List.unmodifiable(children);
CssAtRule(this.name, {this.value, this.span});
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
visitor.visitAtRule(this);
CssAtRule withChildren(Iterable<CssNode> children) =>
new CssAtRule(name, value: value, children: children, span: span);
String toString() {
var buffer = new StringBuffer("@$name");
if (value != null) buffer.write(" $value");

View File

@ -7,7 +7,7 @@ import 'package:source_span/source_span.dart';
import '../../visitor/css.dart';
import 'node.dart';
class CssComment implements CssNode {
class CssComment extends CssNode {
final String text;
final FileSpan span;

View File

@ -9,7 +9,7 @@ import '../../visitor/css.dart';
import 'node.dart';
import 'value.dart';
class CssDeclaration implements CssNode {
class CssDeclaration extends CssNode {
final CssValue<String> name;
final CssValue<Value> value;

View File

@ -5,27 +5,18 @@
import 'package:source_span/source_span.dart';
import '../../visitor/css.dart';
import '../parent.dart';
import 'node.dart';
class CssMediaRule implements CssNode, Parent<CssNode, CssMediaRule> {
class CssMediaRule extends CssParentNode {
final List<CssMediaQuery> queries;
final List<CssNode> children;
final FileSpan span;
// TODO: validate that children contains only at-rule and style rule nodes?
CssMediaRule(Iterable<CssMediaQuery> queries, Iterable<CssNode> children,
{this.span})
: queries = new List.unmodifiable(queries),
children = new List.unmodifiable(children);
CssMediaRule(this.queries, {this.span});
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
visitor.visitMediaRule(this);
CssMediaRule withChildren(Iterable<CssNode> children) =>
new CssMediaRule(queries, children, span: span);
String toString() => "@media ${queries.join(", ")} {${children.join(" ")}}";
}

View File

@ -2,6 +2,8 @@
// MIT-style license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
import 'dart:collection';
import '../../visitor/css.dart';
import '../node.dart';
@ -15,5 +17,39 @@ export 'stylesheet.dart';
export 'value.dart';
abstract class CssNode extends AstNode {
CssParentNode get parent => _parent;
CssParentNode _parent;
int _indexInParent;
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor);
void remove() {
if (_parent == null) {
throw new StateError("Can't remove a node without a parent.");
}
_parent._children.removeAt(_indexInParent);
for (var i = _indexInParent; i < _parent._children.length; i++) {
_parent._children[i]._indexInParent--;
}
}
}
abstract class CssParentNode extends CssNode {
final List<CssNode> children;
final List<CssNode> _children;
CssParentNode() : this._([]);
CssParentNode._(List<CssNode> children)
: _children = children,
children = new UnmodifiableListView<CssNode>(children);
void addChild(CssNode child) {
// TODO: validate that children are valid?
child._parent = this;
child._indexInParent = _children.length;
_children.add(child);
}
}

View File

@ -5,27 +5,19 @@
import 'package:source_span/source_span.dart';
import '../../visitor/css.dart';
import '../parent.dart';
import '../selector.dart';
import 'node.dart';
import 'value.dart';
class CssStyleRule implements CssNode, Parent<CssNode, CssStyleRule> {
class CssStyleRule extends CssParentNode {
final CssValue<SelectorList> selector;
final List<CssNode> children;
final FileSpan span;
// TODO: validate that children contains only at-rule and declaration nodes?
CssStyleRule(this.selector, Iterable<CssNode> children, {this.span})
: children = new List.unmodifiable(children);
CssStyleRule(this.selector, {this.span});
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
visitor.visitStyleRule(this);
CssStyleRule withChildren(Iterable<CssNode> children) =>
new CssStyleRule(selector, children, span: span);
String toString() => "$selector {${children.join(" ")}}";
}

View File

@ -5,22 +5,15 @@
import 'package:source_span/source_span.dart';
import '../../visitor/css.dart';
import '../parent.dart';
import 'node.dart';
class CssStylesheet implements CssNode, Parent<CssNode, CssStylesheet> {
final List<CssNode> children;
class CssStylesheet extends CssParentNode {
final FileSpan span;
CssStylesheet(Iterable<CssNode> children, {this.span})
: children = new List.unmodifiable(children);
CssStylesheet({this.span});
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) =>
visitor.visitStylesheet(this);
CssStylesheet withChildren(Iterable<CssNode> children) =>
new CssStylesheet(children, span: span);
String toString() => children.join(" ");
}

View File

@ -4,17 +4,14 @@
import 'package:source_span/source_span.dart';
import '../../visitor/css.dart';
import 'node.dart';
import '../node.dart';
class CssValue<T> implements CssNode {
class CssValue<T> implements AstNode {
final T value;
final FileSpan span;
CssValue(this.value, {this.span});
/*=T*/ accept/*<T>*/(CssVisitor/*<T>*/ visitor) => visitor.visitValue(this);
String toString() => value.toString();
}

View File

@ -1,11 +0,0 @@
// 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 'node.dart';
abstract class Parent<T extends AstNode, N extends T> extends AstNode {
List<T> get children;
N withChildren(Iterable<T> children);
}

View File

@ -5,11 +5,10 @@
import 'package:source_span/source_span.dart';
import '../../visitor/sass/statement.dart';
import '../parent.dart';
import 'expression/interpolation.dart';
import 'statement.dart';
class AtRule implements Statement, Parent<Statement, AtRule> {
class AtRule implements Statement {
final String name;
final InterpolationExpression value;
@ -26,9 +25,6 @@ class AtRule implements Statement, Parent<Statement, AtRule> {
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitAtRule(this);
AtRule withChildren(Iterable<Statement> children) =>
new AtRule(name, value: value, children: children, span: span);
String toString() {
var buffer = new StringBuffer("@$name");
if (value != null) buffer.write(" $value");

View File

@ -5,10 +5,9 @@
import 'package:source_span/source_span.dart';
import '../../visitor/sass/statement.dart';
import '../parent.dart';
import 'statement.dart';
class MediaRule implements Statement, Parent<Statement, MediaRule> {
class MediaRule implements Statement {
final List<MediaQuery> queries;
final List<Statement> children;
@ -23,8 +22,5 @@ class MediaRule implements Statement, Parent<Statement, MediaRule> {
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitMediaRule(this);
MediaRule withChildren(Iterable<Statement> children) =>
new MediaRule(queries, children, span: span);
String toString() => "@media ${queries.join(", ")} {${children.join(" ")}}";
}

View File

@ -5,11 +5,10 @@
import 'package:source_span/source_span.dart';
import '../../visitor/sass/statement.dart';
import '../parent.dart';
import 'expression/interpolation.dart';
import 'statement.dart';
class StyleRule implements Statement, Parent<Statement, StyleRule> {
class StyleRule implements Statement {
final InterpolationExpression selector;
final List<Statement> children;
@ -24,8 +23,5 @@ class StyleRule implements Statement, Parent<Statement, StyleRule> {
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitStyleRule(this);
StyleRule withChildren(Iterable<Statement> children) =>
new StyleRule(selector, children, span: span);
String toString() => "$selector {${children.join(" ")}}";
}

View File

@ -5,10 +5,9 @@
import 'package:source_span/source_span.dart';
import '../../visitor/sass/statement.dart';
import '../parent.dart';
import 'statement.dart';
class Stylesheet implements Statement, Parent<Statement, Stylesheet> {
class Stylesheet implements Statement {
final List<Statement> children;
final FileSpan span;
@ -19,8 +18,5 @@ class Stylesheet implements Statement, Parent<Statement, Stylesheet> {
/*=T*/ accept/*<T>*/(StatementVisitor/*<T>*/ visitor) =>
visitor.visitStylesheet(this);
Stylesheet withChildren(Iterable<Statement> children) =>
new Stylesheet(children, span: span);
String toString() => children.join(" ");
}

View File

@ -1,66 +0,0 @@
// 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 'dart:collection';
import 'ast/node.dart';
import 'ast/parent.dart';
import 'utils.dart';
class MutableNode<T extends AstNode, N extends T> {
N get node => _node;
void set node(N value) {
_node = value;
_checkNode();
}
N _node;
final MutableNode<T, T> parent;
LinkedListValue<MutableNode<T, N>> _linkedListValue;
Iterable<MutableNode<T, T>> get children =>
_children.map((value) => value.value);
final _children = new LinkedList<LinkedListValue<MutableNode<T, T>>>();
MutableNode.root(this._node) : parent = null {
_checkNode();
}
MutableNode._(this.parent, this._node) {
_checkNode();
}
MutableNode<T, T/*=S*/> add/*<S extends T>*/(T/*=S*/ child) {
var node = new MutableNode<T, T/*=S*/>._(this, child);
node._linkedListValue = new LinkedListValue(node);
_children.add(node._linkedListValue);
return node;
}
void remove() {
_linkedListValue.unlink();
}
void _checkNode() {
if (node is! Parent) return;
var parent = node as Parent;
if (parent.children == null) return;
if (parent.children.isEmpty) return;
throw new StateError("A mutable node can't have immutable children.");
}
N build() {
if (children.isEmpty) return node;
if (node is Parent<T, N>) {
return (node as Parent<T, N>)
.withChildren(children.map((entry) => entry.build()));
}
throw new StateError("Non-Parent $node may not have children.");
}
}

View File

@ -3,7 +3,6 @@
// https://opensource.org/licenses/MIT.
import '../ast/css/node.dart';
import '../value.dart';
import 'value.dart';
abstract class CssVisitor<T> extends ValueVisitor<T> {
@ -38,6 +37,4 @@ abstract class CssVisitor<T> extends ValueVisitor<T> {
}
return null;
}
T visitValue(CssValue node) => node is Value ? node.accept(this) : null;
}

View File

@ -8,6 +8,7 @@ import '../../ast/css/node.dart';
import '../../util/character.dart';
import '../../value.dart';
import '../css.dart';
import '../../parser.dart';
String toCss(CssNode node) {
var visitor = new _SerializeCssVisitor();

View File

@ -7,7 +7,6 @@ import '../../../ast/sass/expression.dart';
import '../../../ast/sass/statement.dart';
import '../../../ast/selector.dart';
import '../../../environment.dart';
import '../../../mutable_node.dart';
import '../../../parser.dart';
import '../../../value.dart';
import '../expression/perform.dart';
@ -27,11 +26,11 @@ class PerformVisitor extends StatementVisitor {
/// This will always have an empty list of children.
CssMediaRule _mediaRule;
/// A mutable view of the root stylesheet node.
MutableNode<CssNode, CssStylesheet> _root;
/// The root stylesheet node.
CssStylesheet _root;
/// A mutable view of the current parent node in the output CSS tree.
MutableNode<CssNode, CssNode> _parent;
/// The current parent node in the output CSS tree.
CssParentNode _parent;
PerformVisitor() : this._(new Environment());
@ -42,16 +41,15 @@ class PerformVisitor extends StatementVisitor {
void visit(Statement node) => node.accept(this);
CssStylesheet visitStylesheet(Stylesheet node) {
_root = new MutableNode<CssNode, CssStylesheet>.root(
new CssStylesheet(const [], span: node.span));
_root = new CssStylesheet(span: node.span);
_parent = _root;
super.visitStylesheet(node);
return _root.build();
return _root;
}
void visitComment(Comment node) {
if (node.isSilent) return;
_parent.add(new CssComment(node.text, span: node.span));
_addChild(new CssComment(node.text, span: node.span));
}
void visitDeclaration(Declaration node) {
@ -66,7 +64,7 @@ class PerformVisitor extends StatementVisitor {
return;
}
_parent.add(new CssDeclaration(name, cssValue, span: node.span));
_addChild(new CssDeclaration(name, cssValue, span: node.span));
}
void visitAtRule(AtRule node) {
@ -75,7 +73,7 @@ class PerformVisitor extends StatementVisitor {
: _performInterpolation(node.value, trim: true);
if (node.children == null) {
_parent.add(new CssAtRule(node.name, value: value, span: node.span));
_addChild(new CssAtRule(node.name, value: value, span: node.span));
}
_withParent(
@ -85,24 +83,25 @@ class PerformVisitor extends StatementVisitor {
}
void visitMediaRule(MediaRule node) {
var queries = node.queries.map(_visitMediaQuery);
if (_mediaRule != null) {
queries = _mergeMediaQueries(_mediaRule.queries, queries);
}
var queryIterable = node.queries.map(_visitMediaQuery);
var queries = _mediaRule == null
? new List<CssMediaQuery>.unmodifiable(queryIterable)
: _mergeMediaQueries(_mediaRule.queries, queryIterable);
if (queries.isEmpty) return;
var rule = new CssMediaRule(queries, const [], span: node.span);
_withParent(rule, () {
_withMediaRule(rule, () => super.visitMediaRule(node));
if (_parent.children.isEmpty) _parent.remove();
}, through: (node) => node is CssStyleRule || node is CssMediaRule);
var rule = new CssMediaRule(queries, span: node.span);
_withParent(
rule,
() => _withMediaRule(rule, () => super.visitMediaRule(node)),
through: (node) => node is CssStyleRule || node is CssMediaRule,
removeIfEmpty: true);
}
List<CssMediaQuery> _mergeMediaQueries(
Iterable<CssMediaQuery> queries1, Iterable<CssMediaQuery> queries2) {
return queries1.expand/*<CssMediaQuery>*/((query1) {
return new List.unmodifiable(queries1.expand/*<CssMediaQuery>*/((query1) {
return queries2.map((query2) => query1.merge(query2));
}).where((query) => query != null).toList();
}).where((query) => query != null));
}
CssMediaQuery _visitMediaQuery(MediaQuery query) {
@ -136,11 +135,12 @@ class PerformVisitor extends StatementVisitor {
new Parser(selectorText.value).parseSelector(),
span: node.selector.span);
var rule = new CssStyleRule(selector, const [], span: node.span);
_withParent(rule, () {
_withStyleRule(rule, () => super.visitStyleRule(node));
if (_parent.children.isEmpty) _parent.remove();
}, through: (node) => node is CssStyleRule);
var rule = new CssStyleRule(selector, span: node.span);
_withParent(
rule,
() => _withStyleRule(rule, () => super.visitStyleRule(node)),
through: (node) => node is CssStyleRule,
removeIfEmpty: true);
}
void visitVariableDeclaration(VariableDeclaration node) {
@ -159,20 +159,27 @@ class PerformVisitor extends StatementVisitor {
CssValue<Value> _performExpression(Expression expression) =>
new CssValue(expression.accept(_expressionVisitor));
/*=T*/ _withParent/*<S extends CssNode, T>*/(CssNode/*=S*/ node,
/*=T*/ callback(), {bool through(CssNode node)}) {
void _addChild(CssNode node) {
_parent.addChild(node);
}
/*=T*/ _withParent/*<S extends CssParentNode, T>*/(
/*=S*/ node, /*=T*/ callback(),
{bool through(CssNode node), bool removeIfEmpty: false}) {
var oldParent = _parent;
// Go up through parents that match [through].
var parent = _parent;
if (through != null) {
while (through(parent.node)) {
while (through(parent)) {
parent = parent.parent;
}
}
_parent = parent.add(node);
parent.addChild(node);
_parent = node;
var result = _environment.scope(callback);
if (removeIfEmpty && node.children.isEmpty) node.remove();
_parent = oldParent;
return result;