Unify strings and identifiers.

The separation makes sense conceptually, but it would make string
functions a lot harder to write.
This commit is contained in:
Natalie Weizenbaum 2016-09-19 09:07:29 -07:00 committed by Natalie Weizenbaum
parent 9ac48fbc05
commit bacc1c4529
13 changed files with 49 additions and 97 deletions

View File

@ -12,7 +12,6 @@ export 'sass/expression/binary_operation.dart';
export 'sass/expression/boolean.dart';
export 'sass/expression/color.dart';
export 'sass/expression/function.dart';
export 'sass/expression/identifier.dart';
export 'sass/expression/list.dart';
export 'sass/expression/map.dart';
export 'sass/expression/null.dart';

View File

@ -1,22 +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 'package:source_span/source_span.dart';
import '../../../visitor/interface/expression.dart';
import '../expression.dart';
import '../interpolation.dart';
class IdentifierExpression implements Expression {
final Interpolation text;
FileSpan get span => text.span;
IdentifierExpression(this.text);
/*=T*/ accept/*<T>*/(ExpressionVisitor/*<T>*/ visitor) =>
visitor.visitIdentifierExpression(this);
String toString() => text.toString();
}

View File

@ -19,24 +19,28 @@ class StringExpression implements Expression {
/// included.
final Interpolation text;
final bool hasQuotes;
FileSpan get span => text.span;
static String quoteText(String text) =>
new StringExpression(new Interpolation([text], null))
new StringExpression(new Interpolation([text], null), quotes: true)
.asInterpolation(static: true)
.asPlain;
StringExpression(this.text);
StringExpression(this.text, {bool quotes}) : hasQuotes = quotes;
/*=T*/ accept/*<T>*/(ExpressionVisitor/*<T>*/ visitor) =>
visitor.visitStringExpression(this);
/// Interpolation that, when evaluated, produces the syntax of the string.
///
/// Unlike [text], his doesn't resolve escapes and does include quotes.
/// Unlike [text], his doesn't resolve escapes and does include quotes for
/// quoted strings.
Interpolation asInterpolation({bool static: false, int quote}) {
quote ??= _bestQuote();
var buffer = new InterpolationBuffer()..writeCharCode(quote);
quote ??= hasQuotes ? null : _bestQuote();
var buffer = new InterpolationBuffer();
if (quote != null) buffer.writeCharCode(quote);
for (var value in text.contents) {
if (value is Interpolation) {
buffer.addInterpolation(value);
@ -63,7 +67,7 @@ class StringExpression implements Expression {
}
}
}
buffer.writeCharCode(quote);
if (quote != null) buffer.writeCharCode(quote);
return buffer.interpolation(text.span);
}

View File

@ -31,7 +31,7 @@ class SelectorList extends Selector {
return new SassList(components.map((complex) {
return new SassList(
complex.components
.map((component) => new SassIdentifier(component.toString())),
.map((component) => new SassString(component.toString())),
ListSeparator.space);
}), ListSeparator.comma);
}

View File

@ -11,5 +11,5 @@ void defineCoreFunctions(Environment environment) {
environment.setFunction(new BuiltInCallable(
"inspect",
new ArgumentDeclaration([new Argument("value")]),
(arguments) => new SassIdentifier(arguments.single.toString())));
(arguments) => new SassString(arguments.single.toString())));
}

View File

@ -101,7 +101,8 @@ abstract class StylesheetParser extends Parser {
Expression _declarationExpression() {
if (lookingAtChildren()) {
return new StringExpression(new Interpolation([], scanner.emptySpan));
return new StringExpression(new Interpolation([], scanner.emptySpan),
quotes: true);
}
return _expression();
@ -1265,7 +1266,8 @@ abstract class StylesheetParser extends Parser {
}
}
return new StringExpression(buffer.interpolation(scanner.spanFrom(start)));
return new StringExpression(buffer.interpolation(scanner.spanFrom(start)),
quotes: true);
}
Expression _hexColorOrID() {
@ -1287,8 +1289,7 @@ abstract class StylesheetParser extends Parser {
var buffer = new InterpolationBuffer();
buffer.writeCharCode($hash);
buffer.addInterpolation(identifier);
return new IdentifierExpression(
buffer.interpolation(scanner.spanFrom(start)));
return new StringExpression(buffer.interpolation(scanner.spanFrom(start)));
}
SassColor _hexColorContents() {
@ -1342,7 +1343,7 @@ abstract class StylesheetParser extends Parser {
return scanner.peekChar() == $lparen
? new FunctionExpression(identifier, _argumentInvocation())
: new IdentifierExpression(identifier);
: new StringExpression(identifier);
}
/// Consumes tokens up to "{", "}", ";", or "!".
@ -1420,7 +1421,7 @@ abstract class StylesheetParser extends Parser {
return buffer.interpolation(scanner.spanFrom(start));
}
IdentifierExpression _interpolatedDeclarationValue() {
StringExpression _interpolatedDeclarationValue() {
// NOTE: this logic is largely duplicated in Parser.declarationValue. Most
// changes here should be mirrored there.
@ -1511,8 +1512,7 @@ abstract class StylesheetParser extends Parser {
}
if (brackets.isNotEmpty) scanner.expectChar(brackets.last);
return new IdentifierExpression(
buffer.interpolation(scanner.spanFrom(start)));
return new StringExpression(buffer.interpolation(scanner.spanFrom(start)));
}
Interpolation _interpolatedIdentifier() {

View File

@ -4,14 +4,12 @@
import 'exception.dart';
import 'value/boolean.dart';
import 'value/identifier.dart';
import 'value/string.dart';
import 'visitor/interface/value.dart';
import 'visitor/serialize.dart';
export 'value/boolean.dart';
export 'value/color.dart';
export 'value/identifier.dart';
export 'value/list.dart';
export 'value/map.dart';
export 'value/null.dart';
@ -56,23 +54,24 @@ abstract class Value {
Value plus(Value other) {
if (other is SassString) {
return new SassString(valueToCss(this) + other.text);
return new SassString(valueToCss(this) + other.text,
quotes: other.hasQuotes);
} else {
return new SassIdentifier(valueToCss(this) + valueToCss(other));
return new SassString(valueToCss(this) + valueToCss(other));
}
}
Value minus(Value other) =>
new SassIdentifier("${valueToCss(this)}-${valueToCss(other)}");
new SassString("${valueToCss(this)}-${valueToCss(other)}");
Value dividedBy(Value other) =>
new SassIdentifier("${valueToCss(this)}/${valueToCss(other)}");
new SassString("${valueToCss(this)}/${valueToCss(other)}");
Value unaryPlus() => new SassIdentifier("+${valueToCss(this)}");
Value unaryPlus() => new SassString("+${valueToCss(this)}");
Value unaryMinus() => new SassIdentifier("-${valueToCss(this)}");
Value unaryMinus() => new SassString("-${valueToCss(this)}");
Value unaryDivide() => new SassIdentifier("/${valueToCss(this)}");
Value unaryDivide() => new SassString("/${valueToCss(this)}");
Value unaryNot() => sassFalse;

View File

@ -1,25 +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 '../visitor/interface/value.dart';
import '../value.dart';
class SassIdentifier extends Value {
final String text;
bool get isBlank => text.isEmpty;
SassIdentifier(this.text);
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor) =>
visitor.visitIdentifier(this);
bool operator ==(other) {
if (other is SassString) return text == other.text;
if (other is SassIdentifier) return text == other.text;
return false;
}
int get hashCode => text.hashCode;
}

View File

@ -9,19 +9,23 @@ import '../value.dart';
class SassString extends Value {
final String text;
SassString(this.text);
final bool hasQuotes;
SassString(this.text, {bool quotes: false}) : hasQuotes = quotes;
/*=T*/ accept/*<T>*/(ValueVisitor/*<T>*/ visitor) =>
visitor.visitString(this);
Value plus(Value other) => new SassString(
text + (other is SassString ? other.text : valueToCss(other)));
bool operator ==(other) {
if (other is SassString) return text == other.text;
if (other is SassIdentifier) return text == other.text;
return false;
Value plus(Value other) {
if (other is SassString) {
return new SassString(text + other.text,
quotes: hasQuotes || other.hasQuotes);
} else {
return new SassString(text + valueToCss(other), quotes: hasQuotes);
}
}
bool operator ==(other) => other is SassString && text == other.text;
int get hashCode => text.hashCode;
}

View File

@ -9,7 +9,6 @@ abstract class ExpressionVisitor<T> {
T visitBooleanExpression(BooleanExpression node);
T visitColorExpression(ColorExpression node);
T visitFunctionExpression(FunctionExpression node);
T visitIdentifierExpression(IdentifierExpression node);
T visitListExpression(ListExpression node);
T visitMapExpression(MapExpression node);
T visitNullExpression(NullExpression node);

View File

@ -7,7 +7,6 @@ import '../../value.dart';
abstract class ValueVisitor<T> {
T visitBoolean(SassBoolean value);
T visitColor(SassColor value);
T visitIdentifier(SassIdentifier value);
T visitList(SassList value);
T visitMap(SassMap value);
T visitNull(SassNull value);

View File

@ -629,9 +629,6 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
}
}
SassIdentifier visitIdentifierExpression(IdentifierExpression node) =>
new SassIdentifier(_performInterpolation(node.text));
SassBoolean visitBooleanExpression(BooleanExpression node) =>
new SassBoolean(node.value);
@ -695,7 +692,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
// TODO: if rest is an arglist that has keywords, error out.
var rest = node.arguments.rest?.accept(this);
if (rest != null) arguments.add(rest);
return new SassIdentifier("$name(${arguments.join(', ')})");
return new SassString("$name(${arguments.join(', ')})");
}
Value _runUserDefinedCallable(CallableInvocation invocation,
@ -805,9 +802,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
void _addRestMap(Map<String, Value> values, SassMap map, FileSpan span) {
map.contents.forEach((key, value) {
if (key is SassIdentifier) {
values[key.text] = value;
} else if (key is SassString) {
if (key is SassString) {
values[key.text] = value;
} else {
throw _exception(
@ -863,7 +858,7 @@ class PerformVisitor implements StatementVisitor, ExpressionVisitor<Value> {
}
SassString visitStringExpression(StringExpression node) =>
new SassString(_performInterpolation(node.text));
new SassString(_performInterpolation(node.text), quotes: node.hasQuotes);
// ## Utilities

View File

@ -161,7 +161,7 @@ class _SerializeCssVisitor
}
void _writeCustomPropertyValue(CssDeclaration node) {
var value = (node.value.value as SassIdentifier).text;
var value = (node.value.value as SassString).text;
var minimumIndentation = _minimumIndentation(value);
if (minimumIndentation == null) {
@ -234,9 +234,6 @@ class _SerializeCssVisitor
_buffer.writeCharCode(hexCharFor(color & 0xF));
}
void visitIdentifier(SassIdentifier value) =>
_buffer.write(value.text.replaceAll("\n", " "));
void visitList(SassList value) {
if (value.isBracketed) {
_buffer.writeCharCode($lbracket);
@ -301,8 +298,11 @@ class _SerializeCssVisitor
_buffer.write(value.value.toString());
}
void visitString(SassString string) =>
_buffer.write(_visitString(string.text));
void visitString(SassString string) {
_buffer.write(string.hasQuotes
? _visitString(string.text)
: string.text.replaceAll("\n", " "));
}
String _visitString(String string, {bool forceDoubleQuote: false}) {
var includesSingleQuote = false;