Search code examples
stringoopsmalltalkgnu-smalltalk

Can I extend built-in String class with my methods


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.


Solution

  • 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).