> On 28 Sep 2017, at 18:50, Nicolas Cellier
> <[email protected]> wrote:
>
>
>
> 2017-09-28 16:20 GMT+02:00 Sven Van Caekenberghe <[email protected]>:
> Hi,
>
> I got into a little office discussion about string interpolation as it is
> done in different programming languages.
>
> In Pharo we have String>>#format: which is pretty nice. It works as follows:
>
> | x y |
> x := 123.
> y := #foo.
> 'x={1} and y={2}' format: { x. y }.
>
> It is also possible to use a dictionary with keys, like this:
>
> | x y |
> x := 123.
> y := #foo.
> 'x={x} and y={y}' format: { #x->x. #y->y } asDictionary.
>
> But this is not true string interpolation as described in [
> https://en.wikipedia.org/wiki/String_interpolation ]. The idea is to write
> the value generating expressions directly inside the strings.
>
> Since in Pharo we add features not by extending the syntax but by adding
> messages I wondered if it could be done for string interpolation. The goal is
> to make the following work:
>
> | x y |
> x := 123.
> y := #foo.
> 'It seems x equals {x} and y equals {y} while Pi is still {Float pi}'
> interpolate.
>
> => 'It seems x equals 123 and y equals foo while Pi is still
> 3.141592653589793'
>
> Here is the implementation I came up with:
>
> String>>#interpolate
> "Format the receiver by interpolating the evaluation of expressions
> in between curly brackets in the context of the sender as in the following
> 3 oneline examples.
> 'Today is {Date today}' interpolate.
> | x | x := 123. 'x equals {x} and pi equals {Float pi}' interpolate.
> 'In {#strings} you can escape \{ by prefixing it with \\' interpolate."
>
> | senderContext |
> senderContext := thisContext sender.
> ^ self class new: self size streamContents: [ :out | | stream |
> stream := self readStream.
> [ stream atEnd ] whileFalse: [ | currentChar |
> (currentChar := stream next) == ${
> ifTrue: [ | expression result |
> expression := stream upTo: $}.
> result := Compiler new
> evaluate: expression in: senderContext to: nil notifying: nil
> ifFail: [ ^ nil ] logged: false.
> out nextPutAll: result asString ]
> ifFalse: [
> currentChar == $\
> ifTrue: [ stream atEnd ifFalse: [ out nextPut: stream next ] ]
> ifFalse: [ out nextPut: currentChar ] ] ] ]
>
> It is a hack that could certainly be improved. And there is of course an
> obvious security problem.
>
> Thoughts ?
>
> Sven
>
>
> Nice!
> The only objection I see is that it may fail in blocks if they don't know
> that they have to refer to outer context, especially once we have clean blocks
Yes, there are probably some edge cases. Error handling is tricky too.
> | outer |
> outer := 'foobar'.
> ^#( 1 2 3 ) collect: [:x | 'loop on value {x} in {outer}' interpolate]
That example works for me in a Playground. How does it fail for you ?