blob: a12ec3067078a056777d1e0e84a1deff7a25c196 [file] [edit]
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
part of '../expression.dart';
/// Converts a runtime Dart [literal] value into an [Expression].
///
/// Supported Dart types are translated into literal expressions.
/// If the [literal] is already an [Expression] it is returned without change to
/// allow operating on a collection of mixed simple literals and more complex
/// expressions.
/// Unsupported inputs invoke the [onError] callback.
Expression literal(Object? literal, {Expression Function(Object)? onError}) {
if (literal is Expression) return literal;
if (literal is bool) return literalBool(literal);
if (literal is num) return literalNum(literal);
if (literal is String) return literalString(literal);
if (literal is List) return literalList(literal);
if (literal is Set) return literalSet(literal);
if (literal is Map) return literalMap(literal);
if (literal == null) return literalNull;
if (onError != null) return onError(literal);
throw UnsupportedError('Not a supported literal type: $literal.');
}
/// Represents the literal value `true`.
const Expression literalTrue = LiteralExpression._('true');
/// Represents the literal value `false`.
const Expression literalFalse = LiteralExpression._('false');
/// Create a literal expression from a boolean [value].
Expression literalBool(bool value) => value ? literalTrue : literalFalse;
/// Represents the literal value `null`.
const Expression literalNull = LiteralExpression._('null');
/// Create a literal expression from a number [value].
Expression literalNum(num value) => LiteralExpression._('$value');
/// Create a literal expression from a string [value].
///
/// Returns an expression for a string formatted `'<value>'`.
///
/// If [raw] is `true` returns an expression that will evaluate to a String
/// containing exactly the same content as [value]. The literal may use single
/// or double quotes, and may not actually be marked raw, depending on the
/// content. All disallowed characters are automatically escaped.
///
/// Passing `raw: true` is recommended and will become the only option in a
/// future release.
Expression literalString(String value, {bool raw = false}) {
if (raw) return LiteralExpression._(_escapeString(value));
final escaped = value.replaceAll('\'', '\\\'').replaceAll('\n', '\\n');
return LiteralExpression._("'$escaped'");
}
String _escapeString(String value) {
final original = value;
var hasSingleQuote = false;
var hasDoubleQuote = false;
var hasDollar = false;
var hasBackslash = false;
var canBeRaw = true;
value = value.replaceAllMapped(_escapeRegExp, (match) {
final char = match[0]!;
if (char == "'") {
hasSingleQuote = true;
return char;
} else if (char == '"') {
hasDoubleQuote = true;
return char;
} else if (char == r'$') {
hasDollar = true;
return char;
} else if (char == r'\') {
hasBackslash = true;
return r'\\';
}
canBeRaw = false;
return _escapeMap[char] ?? _hexLiteral(char);
});
if (canBeRaw && (hasDollar || hasBackslash)) {
if (!hasSingleQuote) return "r'$original'";
if (!hasDoubleQuote) return 'r"$original"';
}
if (!hasDollar) {
if (!hasSingleQuote) return "'$value'";
if (!hasDoubleQuote) return '"$value"';
}
value = value.replaceAll(_dollarQuoteRegexp, r'\');
return "'$value'";
}
/// Given single-character string, return the hex-escaped equivalent.
String _hexLiteral(String input) {
final value = input.runes.single
.toRadixString(16)
.toUpperCase()
.padLeft(2, '0');
return '\\x$value';
}
final _dollarQuoteRegexp = RegExp(r"(?=[$'])");
/// A map from whitespace characters & `\` to their escape sequences.
const _escapeMap = {
'\b': r'\b', // 08 - backspace
'\t': r'\t', // 09 - tab
'\n': r'\n', // 0A - new line
'\v': r'\v', // 0B - vertical tab
'\f': r'\f', // 0C - form feed
'\r': r'\r', // 0D - carriage return
'\x7F': r'\x7F', // delete
};
/// A [RegExp] that matches whitespace characters that must be escaped and
/// single-quote, double-quote, and `$`
final _escapeRegExp = RegExp('[\$\'"\\x00-\\x07\\x0E-\\x1F$_escapeMapRegexp]');
// _escapeMap.keys.map(_hexLiteral).join();
const _escapeMapRegexp = r'\x08\x09\x0A\x0B\x0C\x0D\x7F\x5C';
/// Create a literal `...` operator for use when creating a Map literal.
///
/// *NOTE* This is used as a sentinel when constructing a `literalMap` or a
/// or `literalConstMap` to signify that the value should be spread. Do NOT
/// reuse the value when creating a Map with multiple spreads.
Expression literalSpread() => LiteralSpreadExpression._(false);
/// Create a literal `...?` operator for use when creating a Map literal.
///
/// *NOTE* This is used as a sentinel when constructing a `literalMap` or a
/// or `literalConstMap` to signify that the value should be spread. Do NOT
/// reuse the value when creating a Map with multiple spreads.
Expression literalNullSafeSpread() => LiteralSpreadExpression._(true);
/// Creates a literal list expression from [values].
LiteralListExpression literalList(
Iterable<Object?> values, [
Reference? type,
]) => LiteralListExpression._(false, values.toList(), type);
/// Creates a literal `const` list expression from [values].
LiteralListExpression literalConstList(
List<Object?> values, [
Reference? type,
]) => LiteralListExpression._(true, values, type);
/// Creates a literal set expression from [values].
LiteralSetExpression literalSet(Iterable<Object?> values, [Reference? type]) =>
LiteralSetExpression._(false, values.toSet(), type);
/// Creates a literal `const` set expression from [values].
LiteralSetExpression literalConstSet(Set<Object?> values, [Reference? type]) =>
LiteralSetExpression._(true, values, type);
/// Create a literal map expression from [values].
LiteralMapExpression literalMap(
Map<Object?, Object?> values, [
Reference? keyType,
Reference? valueType,
]) => LiteralMapExpression._(false, values, keyType, valueType);
/// Create a literal `const` map expression from [values].
LiteralMapExpression literalConstMap(
Map<Object?, Object?> values, [
Reference? keyType,
Reference? valueType,
]) => LiteralMapExpression._(true, values, keyType, valueType);
/// Create a literal record expression from [positionalFieldValues] and
/// [namedFieldValues].
LiteralRecordExpression literalRecord(
List<Object?> positionalFieldValues,
Map<String, Object?> namedFieldValues,
) => LiteralRecordExpression._(false, positionalFieldValues, namedFieldValues);
/// Create a literal `const` record expression from [positionalFieldValues] and
/// [namedFieldValues].
LiteralRecordExpression literalConstRecord(
List<Object?> positionalFieldValues,
Map<String, Object?> namedFieldValues,
) => LiteralRecordExpression._(true, positionalFieldValues, namedFieldValues);
/// Represents a literal value in Dart source code.
///
/// For example, `LiteralExpression('null')` should emit `null`.
///
/// Some common literals and helpers are available as methods/fields:
/// * [literal]
/// * [literalBool] and [literalTrue], [literalFalse]
/// * [literalNull]
/// * [literalList] and [literalConstList]
/// * [literalSet] and [literalConstSet]
class LiteralExpression extends Expression {
final String literal;
const LiteralExpression._(this.literal);
@override
R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
visitor.visitLiteralExpression(this, context);
@override
String toString() => literal;
}
class LiteralSpreadExpression extends LiteralExpression {
LiteralSpreadExpression._(bool nullAware)
: super._('...${nullAware ? '?' : ''}');
}
class LiteralListExpression extends Expression {
@override
final bool isConst;
final List<Object?> values;
final Reference? type;
const LiteralListExpression._(this.isConst, this.values, this.type);
@override
R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
visitor.visitLiteralListExpression(this, context);
@override
String toString() => '[${values.map(literal).join(', ')}]';
}
class LiteralSetExpression extends Expression {
@override
final bool isConst;
final Set<Object?> values;
final Reference? type;
const LiteralSetExpression._(this.isConst, this.values, this.type);
@override
R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
visitor.visitLiteralSetExpression(this, context);
@override
String toString() => '{${values.map(literal).join(', ')}}';
}
class LiteralMapExpression extends Expression {
@override
final bool isConst;
final Map<Object?, Object?> values;
final Reference? keyType;
final Reference? valueType;
const LiteralMapExpression._(
this.isConst,
this.values,
this.keyType,
this.valueType,
);
@override
R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
visitor.visitLiteralMapExpression(this, context);
@override
String toString() => '{$values}';
}
class LiteralRecordExpression extends Expression {
@override
final bool isConst;
final List<Object?> positionalFieldValues;
final Map<String, Object?> namedFieldValues;
const LiteralRecordExpression._(
this.isConst,
this.positionalFieldValues,
this.namedFieldValues,
);
@override
R accept<R>(ExpressionVisitor<R> visitor, [R? context]) =>
visitor.visitLiteralRecordExpression(this, context);
@override
String toString() {
final allFields = positionalFieldValues
.map((v) => v.toString())
.followedBy(
namedFieldValues.entries.map((e) => '${e.key}: ${e.value}'),
);
return '(${allFields.join(', ')})';
}
}