| // 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, preferRaw: raw)); | |
| final escaped = value.replaceAll('\'', '\\\'').replaceAll('\n', '\\n'); | |
| return LiteralExpression._("'$escaped'"); | |
| } | |
| String _escapeString(String value, {bool preferRaw = false}) { | |
| 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 (preferRaw && canBeRaw && !hasSingleQuote) { | |
| return "r'$original'"; | |
| } | |
| 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(', ')})'; | |
| } | |
| } |