1

I find that there is no built-in trim (strip) method to remove leading and trailing spaces from strings in the built-in String class. I want to extend it with my functions. Is it possible? Using example here, I tried following code:

String extend [
    trimleading: str [ |ch ret flag|
        ret := str.                 "make a copy of sent string"
        flag := true.
        [flag] whileTrue: [         "while first char is space"
            ch := ret first: 1.     "again test first char"
            ch = ' '                "check if space remaining" 
            ifTrue: [ ret := (ret copyFrom: 2 to: ret size)]    "copy from 2nd char"
            ifFalse: [flag := false] 
            ].
        ^ret                        "return modified string"
        ]
    trim: str [ |ret|
        ret := str. 
        ret := (self trimleading: ret).           "trim leading spaces"
        ret := (self trimleading: (ret reverse)). "reverse string and repeat trim leading"
        ^(ret reverse)                            "return re-reversed string"
        ]
].

oristr := '        this is a test  '
('>>',oristr,'<<') displayNl.
('>>',(oristr trim),'<<') displayNl.

Above code does not work and gives following error:

$ gst string_extend_trim.st 

>>        this is a test  <<
Object: '        this is a test  ' error: did not understand #trim
MessageNotUnderstood(Exception)>>signal (ExcHandling.st:254)
String(Object)>>doesNotUnderstand: #trim (SysExcept.st:1448)
UndefinedObject>>executeStatements (string_extend_trim.st:23)

Where is the problem and how can it be corrected? Thanks.

Edit: Following code works but it does not change original string:

String extend [
    trimleading [ |ch ret flag|
        ret := self.                    "make a copy of sent string"
        flag := true.
        [flag] whileTrue: [             "while first char is space"
            ch := ret first: 1.         "again test first char"
            ch = ' '                    "check if space remaining" 
            ifTrue: [ ret := (ret copyFrom: 2 to: ret size)]    "copy from 2nd char"
            ifFalse: [flag := false] 
            ].
        ^ret                                "return modified string"
        ]
    trim [ |ret|
        ret := self.    
        ret := (self trimleading).  "trim leading spaces"
        ret := ((ret reverse) trimleading ). "reverse string and repeat trim leading"
        ^(ret reverse)                      "return re-reverse string"
        ]
].

oristr := '        this is a test  '
('>>',oristr,'<<') displayNl.
('>>',(oristr trim),'<<') displayNl.
('>>',oristr,'<<') displayNl.
oristr := (oristr trim).
('>>',oristr,'<<') displayNl.

How can oristr trim change oristr? I do not want to write oristr := oristr trim.

rnso
  • 23,686
  • 25
  • 112
  • 234
  • Your first assignment does not make a copy of the String contrary to the comment. The `copyFrom:to:` does, as its name suggests. So in fact you make as many copies as there are leading (and later trailing) spaces. – JayK May 14 '19 at 16:11

2 Answers2

2

The first problem you already solved: originally you defined a method trim: with one argument but sent trim with no arguments.

The second problem is to modify the String in place. You can change the chars with self at: index put: aCharacter and some other methods to copy and overwrite ranges, but you won't be able to change the size (length) of the String. In the Smalltalks I know, Objects cannot change their size after they have been created. Therefore I propose that you stick to making a new String with less characters in trim.

There is a method to exchange one object for another everywhere in the System. It is called become:. But I think you should not use it here. Depending on the Smalltalk implementation you might end up with unwanted side effects, such as replacing a String literal in a method (so the next method invocation would actually run with a different, trimmed string in place of the literal).

JayK
  • 3,006
  • 1
  • 20
  • 26
1

The difference between your code and the example you have linked is that in the example they are extending a custom class, but you are extending a core class. The difference is in the way you should load your code and run it. You should use Packages in GNU-Smalltalk to build it. There is an excellent answer on SO by @lurker how to use extened classes in gst, please read it and upvote it if you like it, I don't want to duplicate the information here.

To adapt your code to String extend:

String extend [
    trimleading: str [ |ch ret flag|
        ret := str.                     "make a copy of sent string"
        flag := true.
        [flag] whileTrue: [         "while first char is space"
            ch := ret first: 1.         "again test first char"
            ch = ' '
            ifTrue: [ ret := (ret copyFrom: 2 to: ret size) ]    "copy from 2nd char"
            ifFalse:  [flag := false ] 
        ].
        ^ ret                                "value is modified string"
    ]
    trim [ | ret |
        ret := self trimleading: self.           "trim leading spaces"
        ret := self trimleading: (ret copy reverse). "reverse string and repeat trim leading"
        ^ (ret reverse)                      "return re-reverse string"
    ]
].

oristr := '        this is a test  '.
('>>',oristr,'<<') displayNl.
('>>',(oristr trim),'<<') displayNl.
('>>',oristr,'<<') displayNl.
oristr := (oristr trim).
('>>',oristr,'<<') displayNl.

You are sending the message #trim to origstr variable so you must define without any parameters. However, that does not apply to #trimleading: so I have taken your previous code and put it there.

Note: You should really read about the self keyword and what it does and understand it - you are using it incorrectly. You assign the ret := self value but you don't use it, you just overwrite it with next assigment.

tukan
  • 17,050
  • 1
  • 20
  • 48