On 29 Sep 2017, at 06:40, Hernán Morales Durand <hernan.morales@gmail.com> wrote:
Hi Sven,
Is this one the similar to the #evaluate in http://www.squeaksource.com/evaluablestrings ?
Haha, yes that is exactly the same trick. Lukas & Philippe back in 2005, duh.
Cheers,
Hernán
2017-09-28 11:20 GMT-03:00 Sven Van Caekenberghe <sven@stfx.eu>:
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