-
Notifications
You must be signed in to change notification settings - Fork 213
Generalized string interpolation #1479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Comparison to extensionsAt first glance, there is not much difference in syntax compared to extensions: final interpolated = json"{$name: $value}";
final withExtension = "{$name: $value}".json; The real benefit is that with the The extension example with correct encoding would therefore simplify. final interpolated = json"{$name: $value}";
final withExtension = "{$name: ${jsonEncode(value)}}".json; All tokens at onceThe proposed API splits parsing strings and interpolated values in two methods which are called in order the "tokens" appear in the interpolated string. abstract class Interpolator<R, T> {
Interpolator<R, T> addString(String string);
Interpolator<R, T> addValue(T value);
R toValue();
} Maybe it would be easier for the implementer to get the full list of "tokens" at once. This would allow to look ahead to decide on the correct encoding for each value. That's also possible with the proposed API by building the list and parsing it when abstract class Interpolator<R, T> {
R toValue(List<Token<T>> tokens);
}
abstract class Token<T> {
bool get isString;
bool get isValue;
String get string;
T get value;
} |
I thought about an API like the It's an extra overhead. If you want it, you can define your own If you want to throw in one of the It's a "push" API, so the caller knows whether it's a string or a value, the compiler can literally turn var myJson = jsn"{ $name: $value }"; into var myJson = jsn.addString("{ ").addValue(name).addString(": ").addValue(value).addString("}").toValue(); (There is an issue with my approach, though. The spread |
I believe the rest parts of interpolated strings outside of |
You can creater a class _GraphemeClusterInterpolator implements Interpolator<Characters, Object?> {
final StringBuffer buffer = StringBuffer();
addString(String value) {
buffer.write(value);
return this;
}
addValue(Object? value) {
buffer.write(value.toString());
return this;
}
toValue() => buffer.toString().characters;
}
Interpolator<Characters, Object?> get gc => _GraphemeClusterInterpolator(); You can also do things like It does mean the the result can't be const, not unless we increase the capability of constant computation significantly. |
Possible use case DateFormatWhile it isn't shorter, it could be a typesafe way to construct a final DateFormat dateFormat = DateFormat("h:mm a");
final DateFormat dateFormatInterpolated =
df"${DfSymbol.hourInAmPm}:${DfSymbol.minuteInHour} ${DfSymbol.amPmMarker}"; class DateFormatInterpolator implements Interpolator<DateFormat, DfSymbol> { }
enum DfSymbol {
// h
hourInAmPm,
// mm
minuteInHour,
// a
amPmMarker
} Multiple interpolated typesThe question I was asking myself whether it would be possible to also inject normal Strings via interpolation in such a date format interpolated string. final String username = account.userName;
final DateFormat dateFormatInterpolated =
df"It's ${DfSymbol.hourInAmPm}:${DfSymbol.minuteInHour} ${DfSymbol.amPmMarker} for $username"; To make it work with the current API we need sum types #83. Then one could write class DateFormatInterpolator implements Interpolator<DateFormat, DfSymbol|String> { } |
It sounds better, but not best, for me. |
I agree that Dart doesn't need JSON-string-literals. Or some kind of template system (like, Dart code generation). It's not perfect for that because it doesn't nest well. If I want to inline a list, I cant just do |
I've been wanting named string templates for a long time. I think I have an internal doc from 2011 proposing it. :) With the static metaprogramming stuff, @jakemac53 and I are considering using them to also provide a nicer syntax for constructing pieces of Dart syntax, like: var e = expr"{}";
var s = stmt"{}"; Using bare strings has some problems because, as in the examples above, the language is ambiguous if you don't know what grammar production you are trying to parse. A Personally, I'm not crazy about the push API you defined. I think it will give templates more flexibility to have a pull API. In particular, I'd rather the interpolated expressions be thunks so that the template handler can choose when/if to evaluate them, handle exceptions coming from them, etc. Of course, if you start talking about wanting to give user code the ability to not evaluate some subexpressions, that starts to look a lot like a macro... So Jake and I have discussed a little about whether some kind of named string template thing should be a compile-time API that gets expanded using static metaprogramming. We don't have anything at all coherent for that yet, though. But, overall, yes, I would love named string templates like this. |
@lrhn can we use encoding constants as prefix: const List<int> hello = utf8'Hello, 世界'; |
String literals are already utf-8 without prefix. |
By the way, there is no definition of the encoding of source code in the spec. |
String values are encoded as UTF-16 (or rather, they are sequences of UTF-16 code units, not necessarily valid UTF-16), not UTF-8. The proposed idea here should be able to create a I'd prefer if it was possible to create the UTF-8 bytes at compile-time instead, but that's probably a job for macros (@jakemac53 - expression macros which expand to something else, yay or nay?)
That's all the spec says, but in practice the compiler only accepts UTF-8. (Just tried with UTF-16 LE/BE with BOM, and no success, it must be UTF-8). |
My idea is to use other encodings as well: json, ascii, ... , myCustomCodec. |
Yes I think expression level macros would be well suited for this (but we haven't attempted to specify them yet) |
There is small discussion of something similar here. A strawman could be:
|
Sorry, I've misunderstood. I'm not sure what specify the character encoding of source code, though. |
@Levi-Lesches dart has @b('HTTP')
external List<int> get HTTP;
// generates:
const List<int> HTTP = <int>[72, 84, 84, 80]; |
FYI, C# supports utf-8 string literal. |
Another option for parametrization is to allow controlling escapes. Consider adding a member int addEscape(Iterable<int> charCodes); which get called when seeing a Maybe the tag can implement one of An easier approach would be to just recognize normal escapes or not, but allow "invalid escapes" and keep them in the strings passed to the tag, rather than remove them. Then the tag processor can interpret them as it wants. |
This format (aesthetically unattractive as it is), also has an unfortunate property: it makes it difficult to control whitespace and indentation. Here's how the program will look in real life: class A {
String generateJson() {
//...
return jsn"""
{ $name: $value,
"other": [$v1, $v2],
"all": [
${for (var i = 0; i < values; i++) ...[if (i > 0) ",", values[i]]}
]
}""";
}
} Any attempt to fix the formatting will lead to unwanted whitespace in the generated json. |
It should give you full control over indentation and whitespace. It might not be convenient to use that control, but whitespace inside interpolations is ignored, and whitespace outside is not. return jsn"""
{ $name: $value,
"other": [$v1, $v2],
"all": [${ for (var i = 0; i < values.length; i++)
...'\n ${values[i]}${if (i > 0) ...','}'
}]
}
"""; (where |
The outside whitespace is a problem. If you want to fix the source formatting in my previous example, you have to write class A {
String generateJson() {
//...
return jsn"""
{ $name: $value,
"other": [$v1, $v2],
"all": [
${for (var i = 0; i < values; i++) ...[if (i > 0) ",", values[i]]}
]
}""";
}
} But then, you have extra spaces at the beginning of each output line. """
|first line
|second line
"""; which removes all whitespace before
But even then, it's not immediately clear how to produce the output where each value in the list is placed on a separate line, with a correct offset, like "all": [
1,
2,
3
] |
Currently string interpolation can only create strings. It's a powerful template mechanism, but it's restricted to creating a string at the end.
If we instead allowed the string parts and values to be collected into a general interface, instead of just something like a
StringBuffer
, then other kinds of "literals" could use the feature.Let's say we defined:
and then allowed the syntax
<postfixExpression> <stringLiteral>
to be used to provide anInterpolator
which is called with the parts, returning a new (or the same) interpolator with the updated state, instead of just turning it all into a string.Example:
This class would allow you to write:
and have all the values which are plugged into the string be JSON encoded first.
An expression of the form
e stringLiteral
would be a compile-time error ife
was not assignable toInterpolator<X, Y>
for someX
andY
. It is a compile-time error if the elements of the interpolation are not assignable toY
. The static type of the expression isX
.The default interpolation is just an implicit
Interpolator<String, Object?>
which does.toString()
on all values and concatenates the strings.Grammar-wise this conflicts with
r"string"
ifr
refers to anInterpolator
. Maybe we need to put a symbol between the two, but all the good symbols are taken. Maybe we could introduce a new syntax instead:<e>"string"
, it just looks a little too much like a type. We could make it a suffix instead, so"{$x:$y}"jsn
, looking more like a RegExp flag. I think it's better for readability to be in front.It would change auto-concatenation of string literals, because that only works for actual strings.
A string literal with a non-default interpolator is not concatenated with any preceding string literals. It may apply to all of the following adjacent string literals.
It might even be possible to extend this to map and collection literals:
with APIs like
abstract class CollectionBuilder<T> { void add(T element); }
andabstract class MapBuilder<K, V> {void operator[](K key, V value); }
. (It's not absolutely clear whether they need atoValue
method as well, which would prevent using the currentSet
/List
/Map
APIs.The text was updated successfully, but these errors were encountered: