String Interpolation

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
40 messages Options
12
Reply | Threaded
Open this post in threaded view
|

String Interpolation

Sven Van Caekenberghe-2
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


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

philippeback

On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
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



Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Thierry Goubier
In reply to this post by Sven Van Caekenberghe-2
Hi Sven,

Jason and Andrew wrote a string interpolation implementation for Andrew's language, Grace, in the parser. It works rather well and isn't too difficult to implement...

The equivalent would be to add it to RB Parser, and, security wise, it would be as vulnerable as calling Compiler evaluate: 'an unsafe string'.

Regards,

Thierry

2017-09-28 16:20 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
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



Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2
In reply to this post by philippeback


> On 28 Sep 2017, at 16:27, [hidden email] wrote:
>
> We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
>
> Phil

Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:

'Today is {1} format: { Date today }.

'Today is { Date today }' interpolate.

> On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> 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
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2
In reply to this post by Thierry Goubier
Thierry,

> On 28 Sep 2017, at 16:38, Thierry Goubier <[hidden email]> wrote:
>
> Hi Sven,
>
> Jason and Andrew wrote a string interpolation implementation for Andrew's language, Grace, in the parser. It works rather well and isn't too difficult to implement...

Like I said, I wanted to add the feature without changing the language (syntax). Using thisContext and the compiler makes that possible.

> The equivalent would be to add it to RB Parser, and, security wise, it would be as vulnerable as calling Compiler evaluate: 'an unsafe string'.

That is what I mean: it is an implicit #evaluate: call, the risk is comparable to an SQL injection attack if the attacker could write the template (not that likely though).

> Regards,
>
> Thierry
>
> 2017-09-28 16:20 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
> 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
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Ron Teitelbaum
In reply to this post by philippeback
Hi All,

To solve this problem I do this: 

replaceTokensIn: aString
^self tokens inject: aString into: [:str :assoc | 
((str explode: assoc key) mergeDelimited: assoc value value)].

tokens is a collection of key value pairs.

aString := 'Dear {!user}'.

self tokens: {'{!user}' -> [self user]}.

self replaceTokensIn: aString.

I like using the blocks because they can be almost anything and since they are defined by the developer the security issue goes away.  It's not a streaming solution which would be nice for a much longer text and it does rewrite the string for every token.  



On Thu, Sep 28, 2017 at 10:27 AM, [hidden email] <[hidden email]> wrote:

On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
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




Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

philippeback
In reply to this post by Sven Van Caekenberghe-2
I stand corrected.

Nuclear style feature then.

'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate

Feels like

STR='sudo reboot'; $(STR)

in bash when one is sudoer.

Phil

On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:


> On 28 Sep 2017, at 16:27, [hidden email] wrote:
>
> We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
>
> Phil

Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:

'Today is {1} format: { Date today }.

'Today is { Date today }' interpolate.

> On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> 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
>
>
>



Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2


> On 28 Sep 2017, at 16:58, [hidden email] wrote:
>
> I stand corrected.
>
> Nuclear style feature then.
>
> 'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate
>
> Feels like
>
> STR='sudo reboot'; $(STR)
>
> in bash when one is sudoer.

Yeah, but it is always the original programmer who writes the template (including the 'Smalltalk snapshot:false andQuit: true' and the 'sudo reboot' in you examples), you would not do that for any good reason.

Typically, you would write something like

  'My name is {firstname} {lastname}' interpolate.

in a method of an object with firstname and lastname as instance variables. To access the binding, #evaluate: is used. Though a malicious person could enter 'Smalltalk snapshot:false andQuit: true' as name, it is not that expression that gets evaluated. So in that respect there is no risk.

The risk would be when the template string itself would be (partially) based on used input.

> Phil
>
> On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:
>
>
> > On 28 Sep 2017, at 16:27, [hidden email] wrote:
> >
> > We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
> >
> > Phil
>
> Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:
>
> 'Today is {1} format: { Date today }.
>
> 'Today is { Date today }' interpolate.
>
> > On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > 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
> >
> >
> >
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2
In reply to this post by Ron Teitelbaum


> On 28 Sep 2017, at 16:55, Ron Teitelbaum <[hidden email]> wrote:
>
> Hi All,
>
> To solve this problem I do this:
>
> replaceTokensIn: aString
> ^self tokens inject: aString into: [:str :assoc |
> ((str explode: assoc key) mergeDelimited: assoc value value)].
>
> tokens is a collection of key value pairs.
>
> aString := 'Dear {!user}'.
>
> self tokens: {'{!user}' -> [self user]}.
>
> self replaceTokensIn: aString.
>
> I like using the blocks because they can be almost anything and since they are defined by the developer the security issue goes away.  It's not a streaming solution which would be nice for a much longer text and it does rewrite the string for every token.  

I think I understand what you mean. When there is no #evaluate: involved, there is no risk.

#explode: and #mergeDelimited: are not standard messages though ...

> On Thu, Sep 28, 2017 at 10:27 AM, [hidden email] <[hidden email]> wrote:
> We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
>
> Phil
>
> On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> 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
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Thierry Goubier
In reply to this post by Sven Van Caekenberghe-2


2017-09-28 17:08 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:


> On 28 Sep 2017, at 16:58, [hidden email] wrote:
>
> I stand corrected.
>
> Nuclear style feature then.
>
> 'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate
>
> Feels like
>
> STR='sudo reboot'; $(STR)
>
> in bash when one is sudoer.

Yeah, but it is always the original programmer who writes the template (including the 'Smalltalk snapshot:false andQuit: true' and the 'sudo reboot' in you examples), you would not do that for any good reason.

Typically, you would write something like

  'My name is {firstname} {lastname}' interpolate.

in a method of an object with firstname and lastname as instance variables. To access the binding, #evaluate: is used. Though a malicious person could enter 'Smalltalk snapshot:false andQuit: true' as name, it is not that expression that gets evaluated. So in that respect there is no risk.

The risk would be when the template string itself would be (partially) based on used input.

which is easy to overlook:

aString interpolate

People are on average a bit more carefull when they use #compile: or #evaluate:.

Thierry
 

> Phil
>
> On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:
>
>
> > On 28 Sep 2017, at 16:27, [hidden email] wrote:
> >
> > We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
> >
> > Phil
>
> Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:
>
> 'Today is {1} format: { Date today }.
>
> 'Today is { Date today }' interpolate.
>
> > On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > 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
> >
> >
> >
>
>
>



Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2


> On 28 Sep 2017, at 17:13, Thierry Goubier <[hidden email]> wrote:
>
>
>
> 2017-09-28 17:08 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
>
>
> > On 28 Sep 2017, at 16:58, [hidden email] wrote:
> >
> > I stand corrected.
> >
> > Nuclear style feature then.
> >
> > 'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate
> >
> > Feels like
> >
> > STR='sudo reboot'; $(STR)
> >
> > in bash when one is sudoer.
>
> Yeah, but it is always the original programmer who writes the template (including the 'Smalltalk snapshot:false andQuit: true' and the 'sudo reboot' in you examples), you would not do that for any good reason.
>
> Typically, you would write something like
>
>   'My name is {firstname} {lastname}' interpolate.
>
> in a method of an object with firstname and lastname as instance variables. To access the binding, #evaluate: is used. Though a malicious person could enter 'Smalltalk snapshot:false andQuit: true' as name, it is not that expression that gets evaluated. So in that respect there is no risk.
>
> The risk would be when the template string itself would be (partially) based on used input.
>
> which is easy to overlook:
>
> aString interpolate
>
> People are on average a bit more carefull when they use #compile: or #evaluate:.
>
> Thierry

100% correct, hence my warning.

> > Phil
> >
> > On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> >
> >
> > > On 28 Sep 2017, at 16:27, [hidden email] wrote:
> > >
> > > We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
> > >
> > > Phil
> >
> > Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:
> >
> > 'Today is {1} format: { Date today }.
> >
> > 'Today is { Date today }' interpolate.
> >
> > > On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > > 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
> > >
> > >
> > >
> >
> >
> >


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

philippeback
From a book about PHP security, there was a chapter about "tainted variables".

The concept is that once a variable is written to from a user, all other variables that come into contact with it should be considered tainted from a security perspective.

The chapter went about how such variables should be toroughly sanitized and always considered dangerous.

So, maybe a TaintedString and TaintedWideString could be introducted with an #isTainted returing true where Object>>#isTainted would be returning false and being overriden in Collection to check if anything that is in the collection is tainted as well.

So one would not be allowed to interpolate tainted variables.

There is more to this but you get the idea

Maybe the VM could be supporting this. We have immutable, so why not tainted?

Phil




On Thu, Sep 28, 2017 at 5:25 PM, Sven Van Caekenberghe <[hidden email]> wrote:


> On 28 Sep 2017, at 17:13, Thierry Goubier <[hidden email]> wrote:
>
>
>
> 2017-09-28 17:08 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
>
>
> > On 28 Sep 2017, at 16:58, [hidden email] wrote:
> >
> > I stand corrected.
> >
> > Nuclear style feature then.
> >
> > 'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate
> >
> > Feels like
> >
> > STR='sudo reboot'; $(STR)
> >
> > in bash when one is sudoer.
>
> Yeah, but it is always the original programmer who writes the template (including the 'Smalltalk snapshot:false andQuit: true' and the 'sudo reboot' in you examples), you would not do that for any good reason.
>
> Typically, you would write something like
>
>   'My name is {firstname} {lastname}' interpolate.
>
> in a method of an object with firstname and lastname as instance variables. To access the binding, #evaluate: is used. Though a malicious person could enter 'Smalltalk snapshot:false andQuit: true' as name, it is not that expression that gets evaluated. So in that respect there is no risk.
>
> The risk would be when the template string itself would be (partially) based on used input.
>
> which is easy to overlook:
>
> aString interpolate
>
> People are on average a bit more carefull when they use #compile: or #evaluate:.
>
> Thierry

100% correct, hence my warning.

> > Phil
> >
> > On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> >
> >
> > > On 28 Sep 2017, at 16:27, [hidden email] wrote:
> > >
> > > We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
> > >
> > > Phil
> >
> > Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:
> >
> > 'Today is {1} format: { Date today }.
> >
> > 'Today is { Date today }' interpolate.
> >
> > > On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > > 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
> > >
> > >
> > >
> >
> >
> >



Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Ron Teitelbaum
In reply to this post by Sven Van Caekenberghe-2
Sorry, I tried to have them included at one point on squeak but I was talked down.

I use them a lot!  They work with other things besides strings.  

Collection >> explode: aDelimiter
"explode the collection into a collection of collections broken by aDelimiter"
"(#(#(1 2) #(3 4)) mergeDelimited: Character tab ) explode: Character tab = an OrderedCollection(#(1 2) #(3 4))
'abcdef' explode: 'cd' = an OrderedCollection('ab' 'ef')"
| resultCollection starting aDelimiterPosition aDelimiterSize |
self ifEmpty: [^self].
resultCollection := OrderedCollection new.
aDelimiterSize := aDelimiter isCollection ifTrue: [aDelimiter size] ifFalse: [1].
starting := 1.
[aDelimiterPosition := aDelimiter isCollection ifTrue: [self indexOfSubCollection: aDelimiter startingAt: starting] ifFalse: [self indexOf: aDelimiter startingAt: starting ifAbsent: [0]].
aDelimiterPosition > 0] whileTrue: [
resultCollection add: (self copyFrom: starting to: aDelimiterPosition - 1).
starting := aDelimiterPosition + aDelimiterSize.
].
resultCollection add: (self copyFrom: starting to: self size).
^resultCollection

Collection >> mergeDelimited: anObject
"return to reciever a collection with each element concatenated to remove imbeded collections"
"#(#(1 2) #(3 4)) mergeDelimited: Character tab = #(1 2 Character tab 3 4),  #('ab' 'cd') mergeDelimited: Character cr = 'ab
cd' "
| returnCollection aSeperator |
self ifEmpty: [^self].
aSeperator := anObject isCollection ifTrue: [anObject] ifFalse: [Array with: anObject].  
returnCollection := self first species new.
self copy from: 1 to: self size -1 do: [:a |
a ifNotNil: [
returnCollection := returnCollection, a, aSeperator
].
].
^returnCollection, self last

On Thu, Sep 28, 2017 at 11:25 AM, Sven Van Caekenberghe <[hidden email]> wrote:


> On 28 Sep 2017, at 17:13, Thierry Goubier <[hidden email]> wrote:
>
>
>
> 2017-09-28 17:08 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
>
>
> > On 28 Sep 2017, at 16:58, [hidden email] wrote:
> >
> > I stand corrected.
> >
> > Nuclear style feature then.
> >
> > 'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate
> >
> > Feels like
> >
> > STR='sudo reboot'; $(STR)
> >
> > in bash when one is sudoer.
>
> Yeah, but it is always the original programmer who writes the template (including the 'Smalltalk snapshot:false andQuit: true' and the 'sudo reboot' in you examples), you would not do that for any good reason.
>
> Typically, you would write something like
>
>   'My name is {firstname} {lastname}' interpolate.
>
> in a method of an object with firstname and lastname as instance variables. To access the binding, #evaluate: is used. Though a malicious person could enter 'Smalltalk snapshot:false andQuit: true' as name, it is not that expression that gets evaluated. So in that respect there is no risk.
>
> The risk would be when the template string itself would be (partially) based on used input.
>
> which is easy to overlook:
>
> aString interpolate
>
> People are on average a bit more carefull when they use #compile: or #evaluate:.
>
> Thierry

100% correct, hence my warning.

> > Phil
> >
> > On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> >
> >
> > > On 28 Sep 2017, at 16:27, [hidden email] wrote:
> > >
> > > We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
> > >
> > > Phil
> >
> > Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:
> >
> > 'Today is {1} format: { Date today }.
> >
> > 'Today is { Date today }' interpolate.
> >
> > > On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > > 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
> > >
> > >
> > >
> >
> >
> >




Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2


> On 28 Sep 2017, at 18:08, Ron Teitelbaum <[hidden email]> wrote:
>
> Sorry, I tried to have them included at one point on squeak but I was talked down.
>
> I use them a lot!  They work with other things besides strings.  

In Pharo we have #join: and #split:

$| join: #('Foo' 'Bar').

 => 'Foo|Bar'

$| split: 'Foo|Bar'.

 => an OrderedCollection('Foo' 'Bar')

Not 100% the same, but quite close. Handy indeed.

> Collection >> explode: aDelimiter
> "explode the collection into a collection of collections broken by aDelimiter"
> "(#(#(1 2) #(3 4)) mergeDelimited: Character tab ) explode: Character tab = an OrderedCollection(#(1 2) #(3 4))
> 'abcdef' explode: 'cd' = an OrderedCollection('ab' 'ef')"
> | resultCollection starting aDelimiterPosition aDelimiterSize |
> self ifEmpty: [^self].
> resultCollection := OrderedCollection new.
> aDelimiterSize := aDelimiter isCollection ifTrue: [aDelimiter size] ifFalse: [1].
> starting := 1.
> [aDelimiterPosition := aDelimiter isCollection ifTrue: [self indexOfSubCollection: aDelimiter startingAt: starting] ifFalse: [self indexOf: aDelimiter startingAt: starting ifAbsent: [0]].
> aDelimiterPosition > 0] whileTrue: [
> resultCollection add: (self copyFrom: starting to: aDelimiterPosition - 1).
> starting := aDelimiterPosition + aDelimiterSize.
> ].
> resultCollection add: (self copyFrom: starting to: self size).
> ^resultCollection
>
> Collection >> mergeDelimited: anObject
> "return to reciever a collection with each element concatenated to remove imbeded collections"
> "#(#(1 2) #(3 4)) mergeDelimited: Character tab = #(1 2 Character tab 3 4),  #('ab' 'cd') mergeDelimited: Character cr = 'ab
> cd' "
> | returnCollection aSeperator |
> self ifEmpty: [^self].
> aSeperator := anObject isCollection ifTrue: [anObject] ifFalse: [Array with: anObject].  
> returnCollection := self first species new.
> self copy from: 1 to: self size -1 do: [:a |
> a ifNotNil: [
> returnCollection := returnCollection, a, aSeperator
> ].
> ].
> ^returnCollection, self last
>
> On Thu, Sep 28, 2017 at 11:25 AM, Sven Van Caekenberghe <[hidden email]> wrote:
>
>
> > On 28 Sep 2017, at 17:13, Thierry Goubier <[hidden email]> wrote:
> >
> >
> >
> > 2017-09-28 17:08 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
> >
> >
> > > On 28 Sep 2017, at 16:58, [hidden email] wrote:
> > >
> > > I stand corrected.
> > >
> > > Nuclear style feature then.
> > >
> > > 'Let me get out of here {Smalltalk snapshot:false andQuit: true}. BOOM' interpolate
> > >
> > > Feels like
> > >
> > > STR='sudo reboot'; $(STR)
> > >
> > > in bash when one is sudoer.
> >
> > Yeah, but it is always the original programmer who writes the template (including the 'Smalltalk snapshot:false andQuit: true' and the 'sudo reboot' in you examples), you would not do that for any good reason.
> >
> > Typically, you would write something like
> >
> >   'My name is {firstname} {lastname}' interpolate.
> >
> > in a method of an object with firstname and lastname as instance variables. To access the binding, #evaluate: is used. Though a malicious person could enter 'Smalltalk snapshot:false andQuit: true' as name, it is not that expression that gets evaluated. So in that respect there is no risk.
> >
> > The risk would be when the template string itself would be (partially) based on used input.
> >
> > which is easy to overlook:
> >
> > aString interpolate
> >
> > People are on average a bit more carefull when they use #compile: or #evaluate:.
> >
> > Thierry
>
> 100% correct, hence my warning.
>
> > > Phil
> > >
> > > On Thu, Sep 28, 2017 at 4:43 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > >
> > >
> > > > On 28 Sep 2017, at 16:27, [hidden email] wrote:
> > > >
> > > > We also have http://norbert.hartl.name/blog/2013/10/03/mustache-templates-for-smalltalk/
> > > >
> > > > Phil
> > >
> > > Yes, Mustache is a cool templating engine, but it is similar to #format: not to #interpolate. With true string interpolation, you do not provide a context, you just write the expressions inline. Compare the following two:
> > >
> > > 'Today is {1} format: { Date today }.
> > >
> > > 'Today is { Date today }' interpolate.
> > >
> > > > On Thu, Sep 28, 2017 at 4:20 PM, Sven Van Caekenberghe <[hidden email]> wrote:
> > > > 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
> > > >
> > > >
> > > >
> > >
> > >
> > >
>
>
>
>


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Nicolas Cellier
In reply to this post by Sven Van Caekenberghe-2


2017-09-28 16:20 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
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

| outer |
outer := 'foobar'.
^#( 1 2 3 ) collect: [:x | 'loop on value {x} in {outer}' interpolate]

Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2


> On 28 Sep 2017, at 18:50, Nicolas Cellier <[hidden email]> wrote:
>
>
>
> 2017-09-28 16:20 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
> 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 ?
Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Nicolas Cellier
Hi Sven,
for now it works because the context into which the block is executed still has full access to the variables and receiver of outer context from which the block is declared.

But it's not necessarily the case in all dialects.
In VW for example, I get nil instead of foobar
#('loop on value 1 in nil' 'loop on value 2 in nil' 'loop on value 3 in nil')

This is because the optimized block has its own method (a CompiledBlock) and a restricted context (a BlockContext).
The receiver is the BlockClosure and this closure has no copiedValues from the outerContext and an outerContext set to nil because some analyzer in the compilation phase thought the closure would never access the outerContext (no return to outerContext) nor any of its variable.

Once we'll have clean blocks - depending on the implementation - things might change in Pharo too.
But we must ask Clement on this subject (I add not taken time to check the implementation he proposes).

2017-09-28 19:03 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:


> On 28 Sep 2017, at 18:50, Nicolas Cellier <[hidden email]> wrote:
>
>
>
> 2017-09-28 16:20 GMT+02:00 Sven Van Caekenberghe <[hidden email]>:
> 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 ?

Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

hernanmd
In reply to this post by Sven Van Caekenberghe-2
Hi Sven,

Is this one the similar to the #evaluate in
http://www.squeaksource.com/evaluablestrings ?

Cheers,

Hernán


2017-09-28 11:20 GMT-03:00 Sven Van Caekenberghe <[hidden email]>:

> 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
>
>

Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Pavel Krivanek-3
In reply to this post by Sven Van Caekenberghe-2
This solution will not work for environments without sources too where names like t1, t2 are used for temporary variables.
Anyway, nice idea.

-- Pavel

Dne čtvrtek 28. září 2017 Sven Van Caekenberghe <[hidden email]> napsal(a):
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


Reply | Threaded
Open this post in threaded view
|

Re: String Interpolation

Sven Van Caekenberghe-2
In reply to this post by hernanmd


> On 29 Sep 2017, at 06:40, Hernán Morales Durand <[hidden email]> 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 <[hidden email]>:
>> 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
>>
>>
>


12