CharacterArray.st
changeset 24541 e404499bb36c
parent 24539 3a0f2a4b59d7
child 24549 fd6df841796c
--- a/CharacterArray.st	Sun Aug 11 23:49:41 2019 +0200
+++ b/CharacterArray.st	Mon Aug 12 11:52:59 2019 +0200
@@ -7686,11 +7686,14 @@
         ignoreNonNumericEscapes:true
         ignoreSpecialEscapes:true
         requireParentheses:true
+        ifKeyAbsent:nil
         on:stream.
     ^ stream contents.
 
     "
      'hello %1' expandNumericPlaceholdersWith:#('world')          
+     'hello %(1)' expandNumericPlaceholdersWith:#('world')          
+     'hello %(10)' expandNumericPlaceholdersWith:#('world')          
      'hello %1 %abc' expandNumericPlaceholdersWith:#('world')          
      'hello %1 %(abc)' expandNumericPlaceholdersWith:#('world')          
     "
@@ -7705,11 +7708,30 @@
     "Modified: / 14-07-2018 / 09:23:31 / Claus Gittinger"
 !
 
+expandPlaceholders
+    "return a copy of the receiver, where %<special> escapes are expanded.
+     %<..>
+        Insert a character constant or character sequence, being one of:
+            cr nl tab return lf crlf ff null backspace bell esc newPage space
+        i.e. you can use %<cr> to insert a CR, and %<tab> to insert a TAB.
+
+     See also bindWith:... for VisualAge compatibility."
+
+    ^ self expandPlaceholdersWith:nil
+
+    "
+     'hello a%<crlf>b' expandPlaceholders
+    "
+
+    "Modified: / 01-07-1997 / 00:53:24 / cg"
+    "Modified: / 14-07-2018 / 09:23:31 / Claus Gittinger"
+!
+
 expandPlaceholders:escapeCharacter with:argArrayOrDictionary
-    "this is the generic version of the old %-escaping method, allowing for an arbitrary
+    "this is a more general version of the old %-escaping method, allowing for an arbitrary
      escape character to be used (typically $$ or $% are effectively used).
 
-     Return a copy of the receiver, where all %i escapes are
+     Returns a copy of the receiver, where all %i escapes are
      replaced by corresponding arguments' printStrings from the argArrayOrDictionary.
      I.e. 'hello %1; how is %2' expandPlaceholdersWith:#('world' 'this') results
      in the new string 'hello world; how is this'.
@@ -7730,6 +7752,7 @@
          ignoreNonNumericEscapes:false
          ignoreSpecialEscapes:false
          requireParentheses:true 
+         ifKeyAbsent:nil
          on:stream.
     ^ stream contents.
 
@@ -7805,6 +7828,118 @@
         ignoreNonNumericEscapes:ignoreNonNumericEscapes
         ignoreSpecialEscapes:ignoreSpecialEscapes
         requireParentheses:requireParentheses 
+        ifKeyAbsent:nil
+        on:stream.
+    ^ stream contents
+
+    "
+     'hello %1' expandPlaceholders:$% with:#('world') on:Transcript.
+     'hello %a%<cr>' expandPlaceholders:$% 
+                    with:(Dictionary new at:'a' put:'world';yourself) 
+                    on:Transcript.
+     'hello %aa%<cr>' expandPlaceholders:$% with:(Dictionary new at:'a' put:'world';yourself) on:Transcript.
+     'hello %(aa)%<cr>' expandPlaceholders:$% with:(Dictionary new at:'aa' put:'world';yourself) on:Transcript.
+     'hello %aa%<cr>' expandPlaceholders:$% with:(Dictionary new at:'aa' put:'world';yourself) ignoreNumericEscapes:true requireParentheses:false on:Transcript.
+
+     String streamContents:[:s|
+        'hello %1' expandPlaceholders:$% with:#('world') on:s.
+        s cr.
+        'hello $1; how is $2' expandPlaceholders:$$ with:#('world' 'this') on:s.
+        s cr.
+        'hello %2; how is %1' expandPlaceholders:$% with:#('world' 'this') on:s.
+        s cr.
+        '%1 plus %2 gives %3 ' expandPlaceholders:$% with:#(4 5 9) on:s.
+        s cr.
+        '%%(1)0 gives %(1)0' expandPlaceholders:$% with:#(123) on:s.
+        s cr.
+        '%%10 gives %10' expandPlaceholders:$% with:#(123) on:s.
+        s cr.
+        '%%(10) gives %(10) %<cr>%<tab>next line' expandPlaceholders:$% with:#(123) on:s.
+        s cr.
+        '%%test gives %test' expandPlaceholders:$% with:#(123) on:s.
+        s cr.
+        '|%%<tab>|%%1|%%<cr>| gives |%<tab>|%1|%<cr>|' expandPlaceholders:$% with:#(foo) on:s.
+     ]
+    "
+
+    "without xlation dictionary:
+        'hello %1' expandPlaceholdersWith:nil.
+        'hello%<cr> %1' expandPlaceholdersWith:nil.
+    "
+
+    "
+     |dict|
+
+     dict := Dictionary new.
+     dict at:1 put:'one'.
+     dict at:$a put:'AAAAA'.
+     dict at:$b put:[ Time now ].
+     String streamContents:[:s|
+         'hello $1 $a $b' expandPlaceholders:$$ with:dict on:s.
+     ].
+    "
+
+    "using blocks:
+     |dict|
+
+     dict := Dictionary new.
+     dict at:'time' put:[Time now printString].
+     dict at:'date' put:[Date today printString].
+     String streamContents:[:s|
+         'it is $(time) $(date)' expandPlaceholders:$$ with:dict on:s.
+     ].
+    "
+
+    "Created: / 14-01-2019 / 17:43:03 / Claus Gittinger"
+    "Modified: / 05-06-2019 / 17:05:47 / Claus Gittinger"
+!
+
+expandPlaceholders:escapeCharacter with:argArrayOrDictionary 
+    ignoreNumericEscapes:ignoreNumericEscapes 
+    ignoreNonNumericEscapes:ignoreNonNumericEscapes
+    ignoreSpecialEscapes:ignoreSpecialEscapes
+    requireParentheses:requireParentheses
+    ifKeyAbsent:ifKeyAbsentBlockOrNil
+
+    "this is the generic version of the old %-escaping method, allowing for an arbitrary
+     escape character to be used (typically $$ or $% are effectively used).
+
+     Return a copy of the receiver, where all %i escapes are
+     replaced by corresponding arguments' printStrings from the argArray.
+     I.e. 'hello %1; how is %2' expandPlaceholdersWith:#('world' 'this') results
+     in the new string 'hello world; how is this'.
+
+     As an extension, the argument may also be a dictionary, providing values for symbolic keys.
+     In this case, %a .. %z and %(...) are also allowed.
+     (%1..%9 require a numeric key in the dictionary, however)
+
+     Also, values in argArrayOrDictionary may be blocks.
+
+     To get a '%' character, use a '%%'-escape.
+     To get an integer-indexed placeHolder followed by another digit,
+     or an index > 9, you must use %(digit).
+
+     See also bindWith:... for VisualAge compatibility.
+     Use %<cr> to insert a CR and %<tab> to insert a TAB.
+
+     ignoreNumericEscapes controls if %<nr> escapes are expanded or not.
+     This is required for Windows batch-script expansion, where %<nr> should be left unchanged.
+
+     requireParentheses controls if $abc is allowed or not.
+     If true, multi-character replacements need to be parenthized as $(abc);
+     if false, you can also write $abc.
+    "
+
+    |stream|
+
+    stream := (TextStream ? CharacterWriteStream) on:(self species uninitializedNew:self size + 20).
+    self 
+        expandPlaceholders:escapeCharacter with:argArrayOrDictionary 
+        ignoreNumericEscapes:ignoreNumericEscapes 
+        ignoreNonNumericEscapes:ignoreNonNumericEscapes
+        ignoreSpecialEscapes:ignoreSpecialEscapes
+        requireParentheses:requireParentheses 
+        ifKeyAbsent:ifKeyAbsentBlockOrNil
         on:stream.
     ^ stream contents
 
@@ -7874,7 +8009,8 @@
     ignoreNumericEscapes:ignoreNumericEscapes 
     ignoreNonNumericEscapes:ignoreNonNumericEscapes
     ignoreSpecialEscapes:ignoreSpecialEscapes
-    requireParentheses:requireParentheses 
+    requireParentheses:requireParentheses
+    ifKeyAbsent:replaceActionOrNil
     on:aStream
 
     "this is the central method for %-escaping, allowing for an arbitrary
@@ -7896,24 +8032,49 @@
      or an index > 9, you must use %(digit).
 
      See also bindWith:... for VisualAge compatibility.
-     Use %<cr> to insert a CR and %<tab> to insert a TAB.
-
-     ignoreNumericEscapes controls if %<nr> escapes are expanded or not.
-     This is required for Windows batch-script expansion, where %<nr> should be left unchanged.
-
-     ignoreSpecialEscapes controls if control characters like %<cr> are expanded or not.
-
-     requireParentheses controls if $abc is allowed or not.
-     If true, multi-character replacements need to be parenthized as $(abc) and the above is
-     interpreted as $(a)bc;
-     if false, you can also write $abc.
+
+     - %<..>
+        Insert a character constant or character sequence, being one of:
+            cr nl tab return lf crlf ff null backspace bell esc newPage space
+        i.e. you can use %<cr> to insert a CR, and %<tab> to insert a TAB.
+
+     - ignoreNumericEscapes 
+        controls if %<nr> escapes are expanded or not.
+        This is required for Windows batch-script expansion, where %<nr> should be left unchanged.
+
+     - ignoreSpecialEscapes 
+        controls if control characters like %<cr> are expanded or not.
+
+     - requireParentheses 
+        controls if $abc is allowed or not.
+        If true, multi-character replacements need to be parenthized as $(abc),
+                and the above is interpreted as $(a)bc
+        If false, you can also write $abc.
+
+     - keepIfNoSuchKey 
+        controls what should happen if a variable/index is encountered which is not found in argArrayOrDictionary. 
+        It can be nil or a two arg block.
+        If nil, the sequence is replaced by an empty string (i.e. 'abc$(foo)def' -> 'abcdef')
+        if aBlock, it will be called with both the full escape sequence and the cariable only as arguments,
+        and the expansion will be what the block returns. 
+        i.e. if the block is [:meta :name | meta], then the above will result in 'abc$(foo)def'
+        and if the block is [:meta :name | name], then the above will result in 'abcfoodef'
+        Usefull if you want to expand a string twice, without loosing the key-sequences in the first place.
+        Notice: for supid backward compatibility, keepIfNoSuchKey is not applied for %X sequences, where X is a single letter.
     "
 
     |next v key numericKey
      idx   "{ SmallInteger }"
      idx2  "{ SmallInteger }"
      start "{ SmallInteger }"
-     stop  "{ SmallInteger }"|
+     stop  "{ SmallInteger }"
+     noReplacementAction|
+
+    replaceActionOrNil isNil ifTrue:[
+        noReplacementAction := [:seq :var | '']
+    ] ifFalse:[
+        noReplacementAction := replaceActionOrNil
+    ].
 
     stop := self size.
     start := 1.
@@ -7942,19 +8103,29 @@
                     key := self copyFrom:idx+2 to:idx2-1.
                     idx := idx2 - 1.
                     key := key asSymbolIfInterned.
-                    (#(cr tab nl return lf ff null) includesIdentical:key) ifTrue:[
+                    (#(cr tab nl return lf ff null backspace bell esc newPage space) includesIdentical:key) ifTrue:[
                         aStream nextPut:(Character perform:key).
+                    ] ifFalse:[
+                        (key == #crlf) ifTrue:[
+                            aStream nextPutAll:(String crlf).
+                        ] ifFalse:[
+                            aStream nextPutAll:key.
+                        ]
                     ].
                 ].
             ] ifFalse:[
                 argArrayOrDictionary isNil ifTrue:[
-                    aStream nextPut:escapeCharacter.
+                    "/ %x but no dictionary provided (strange error case, actually)
                     aStream nextPut:next.
                 ] ifFalse:[    
                     (next isDigit and:[ignoreNumericEscapes not]) ifTrue:[
-                        v := argArrayOrDictionary at:(next digitValue) ifAbsent:''
+                        "/ %N (N is digit) 
+                        v := argArrayOrDictionary 
+                                at:(next digitValue) 
+                                ifAbsent:[ noReplacementAction value:escapeCharacter,next value:next ]
                     ] ifFalse:[
                         next == $( ifTrue:[
+                            "/ %(name) 
                             idx2 := self indexOf:$) startingAt:idx+2.
                             self assert:(idx2 > 0) message:'closing parenthesis missing'.
                             key := self copyFrom:idx+2 to:idx2-1.
@@ -7966,7 +8137,9 @@
                                 ignoreNumericEscapes ifTrue:[
                                     v := escapeCharacter,'(',key,')'
                                 ] ifFalse:[
-                                    v := argArrayOrDictionary at:numericKey ifAbsent:''
+                                    v := argArrayOrDictionary 
+                                            at:numericKey 
+                                            ifAbsent:[ noReplacementAction value:escapeCharacter,'(',key,')' value:key ]
                                 ]
                             ] ifFalse:[
                                 ignoreNonNumericEscapes ifTrue:[
@@ -7985,7 +8158,7 @@
                                                 (key size == 1 and:[ argArrayOrDictionary includesKey:(key at:1)]) ifTrue:[
                                                     v := argArrayOrDictionary at:(key at:1)
                                                 ] ifFalse:[
-                                                    v := ''
+                                                    v := noReplacementAction value:escapeCharacter,'(',key,')' value:key
                                                 ]
                                             ].
                                         ].
@@ -7997,7 +8170,7 @@
                               and:[ next isLetter 
                               and:[ argArrayOrDictionary isSequenceable not "is a Dictionary"]]
                             ) ifTrue:[
-                                "so next is a non-numeric single character."
+                                "%X (X is letter)"
                                 requireParentheses ifTrue:[
                                     key := next.
                                 ] ifFalse:[
@@ -8016,7 +8189,9 @@
                                                 "try symbol or string instead of character"
                                                 argArrayOrDictionary
                                                     at:key asString asSymbolIfInternedOrSelf
-                                                    ifAbsent:[escapeCharacter asString , key].
+                                                    ifAbsent:[
+                                                        escapeCharacter asString , key
+                                                    ].
                                          ].
                                 ].
                             ] ifFalse:[
@@ -8097,6 +8272,57 @@
     "Modified: / 05-06-2019 / 17:05:47 / Claus Gittinger"
 !
 
+expandPlaceholders:escapeCharacter 
+    with:argArrayOrDictionary 
+    ignoreNumericEscapes:ignoreNumericEscapes 
+    ignoreNonNumericEscapes:ignoreNonNumericEscapes
+    ignoreSpecialEscapes:ignoreSpecialEscapes
+    requireParentheses:requireParentheses 
+    on:aStream
+
+    "this is the central method for %-escaping, allowing for an arbitrary
+     escape character to be used (typically $$ or $% are effectively used).
+
+     Write the receiver to aStream, where all %i escapes are
+     replaced by corresponding arguments' printStrings from the argArray.
+     I.e. 'hello %1; how is %2' expandPlaceholdersWith:#('world' 'this') results
+     in the new string 'hello world; how is this'.
+
+     As an extension, the argument may also be a dictionary, providing values for symbolic keys.
+     In this case, %a .. %z and %(...) are also allowed.
+     (%1..%9 require a numeric key in the dictionary, however)
+
+     Also, values in argArrayOrDictionary may be blocks.
+
+     To get a '%' character, use a '%%'-escape.
+     To get an integer-indexed placeHolder followed by another digit,
+     or an index > 9, you must use %(digit).
+
+     See also bindWith:... for VisualAge compatibility.
+     Use %<cr> to insert a CR and %<tab> to insert a TAB.
+
+     ignoreNumericEscapes controls if %<nr> escapes are expanded or not.
+     This is required for Windows batch-script expansion, where %<nr> should be left unchanged.
+
+     ignoreSpecialEscapes controls if control characters like %<cr> are expanded or not.
+
+     requireParentheses controls if $abc is allowed or not.
+     If true, multi-character replacements need to be parenthized as $(abc) and the above is
+     interpreted as $(a)bc;
+     if false, you can also write $abc.
+    "
+
+    ^ self
+        expandPlaceholders:escapeCharacter 
+        with:argArrayOrDictionary 
+        ignoreNumericEscapes:ignoreNumericEscapes 
+        ignoreNonNumericEscapes:ignoreNonNumericEscapes
+        ignoreSpecialEscapes:ignoreSpecialEscapes
+        requireParentheses:requireParentheses 
+        ifKeyAbsent:nil
+        on:aStream
+!
+
 expandPlaceholders:escapeCharacter with:argArrayOrDictionary 
     ignoreNumericEscapes:ignoreNumericEscapes 
     on:aStream
@@ -8132,6 +8358,7 @@
         ignoreNonNumericEscapes:false
         ignoreSpecialEscapes:false
         requireParentheses:true
+        ifKeyAbsent:nil
         on:aStream
 
     "
@@ -8188,46 +8415,37 @@
     "Modified (comment): / 14-01-2019 / 17:44:02 / Claus Gittinger"
 !
 
-expandPlaceholders:escapeCharacter with:argArrayOrDictionary 
+expandPlaceholders:escapeCharacter 
+    with:argArrayOrDictionary 
     ignoreNumericEscapes:ignoreNumericEscapes 
     requireParentheses:requireParentheses
-    "this is the generic version of the old %-escaping method, allowing for an arbitrary
+
+    "this is a more general version of the old %-escaping method, allowing for an arbitrary
      escape character to be used (typically $$ or $% are effectively used).
-
-     Return a copy of the receiver, where all %i escapes are
-     replaced by corresponding arguments' printStrings from the argArray.
-     I.e. 'hello %1; how is %2' expandPlaceholdersWith:#('world' 'this') results
-     in the new string 'hello world; how is this'.
-
-     As an extension, the argument may also be a dictionary, providing values for symbolic keys.
-     In this case, %a .. %z and %(...) are also allowed.
-     (%1..%9 require a numeric key in the dictionary, however)
-
-     Also, values in argArrayOrDictionary may be blocks.
-
-     To get a '%' character, use a '%%'-escape.
-     To get an integer-indexed placeHolder followed by another digit,
-     or an index > 9, you must use %(digit).
-
      See also bindWith:... for VisualAge compatibility.
-     Use %<cr> to insert a CR and %<tab> to insert a TAB.
-
-     ignoreNumericEscapes controls if %<nr> escapes are expanded or not.
-     This is required for Windows batch-script expansion, where %<nr> should be left unchanged.
-
-     requireParentheses controls if $abc is allowed or not.
-     If true, multi-character replacements need to be parenthized as $(abc);
-     if false, you can also write $abc.
+
+     - ignoreNumericEscapes 
+        controls if %<nr> escapes are expanded or not.
+        This is required for Windows batch-script expansion, where %<nr> should be left unchanged.
+
+     - requireParentheses 
+        controls if $abc is allowed or not.
+        If true, multi-character replacements need to be parenthized as $(abc);
+        if false, you can also write $abc.
     "
 
     |stream|
 
     stream := (TextStream ? CharacterWriteStream) on:(self species uninitializedNew:self size + 20).
-    self expandPlaceholders:escapeCharacter with:argArrayOrDictionary 
+    self 
+        expandPlaceholders:escapeCharacter 
+        with:argArrayOrDictionary 
         ignoreNumericEscapes:ignoreNumericEscapes 
         ignoreNonNumericEscapes:false
         ignoreSpecialEscapes:false
-        requireParentheses:requireParentheses on:stream.
+        requireParentheses:requireParentheses 
+        ifKeyAbsent:nil
+        on:stream.
     ^ stream contents
 
     "
@@ -8326,11 +8544,14 @@
      if false, you can also write $abc.
     "
 
-    self expandPlaceholders:escapeCharacter with:argArrayOrDictionary 
+    self 
+        expandPlaceholders:escapeCharacter 
+        with:argArrayOrDictionary 
         ignoreNumericEscapes:ignoreNumericEscapes 
         ignoreNonNumericEscapes:false
         ignoreSpecialEscapes:false
         requireParentheses:requireParentheses 
+        ifKeyAbsent:nil
         on:aStream.
 
     "
@@ -8423,6 +8644,7 @@
         ignoreNonNumericEscapes:false 
         ignoreSpecialEscapes:false 
         requireParentheses:true
+        ifKeyAbsent:nil
         on:aStream
 
     "
@@ -8503,6 +8725,7 @@
         ignoreNonNumericEscapes:false
         ignoreSpecialEscapes:false
         requireParentheses:true
+        ifKeyAbsent:nil
         on:stream.
     ^ stream contents.
 
@@ -8538,6 +8761,38 @@
     "Modified: / 14-07-2018 / 09:23:31 / Claus Gittinger"
 !
 
+expandPlaceholdersWith:argArrayOrDictionary ifKeyAbsent:ifNoSuchKeyActionOrNil
+    "return a copy of the receiver, where all %i escapes are
+     replaced by corresponding arguments' printStrings from the argArrayOrDictionary.
+     I.e. 'hello %1; how is %2' expandPlaceholdersWith:#('world' 'this') results
+     in the new string 'hello world; how is this'.
+     The argument may also be a dictionary, providing values for symbolic keys.
+     To get a '%' character, use a '%%'-escape.
+
+     See the comment in
+        expandPlaceholders:with:ignoreNumericEscapes:ignoreNonNumericEscapes:ignoreSpecialEscapes:requireParentheses:ifKeyAbsent:on:
+     for a full explanation.
+
+     See also bindWith:... for VisualAge compatibility."
+
+    |stream|
+
+    stream := (TextStream ? CharacterWriteStream) on:(self species uninitializedNew:self size + 20).
+    self 
+        expandPlaceholders:$% with:argArrayOrDictionary 
+        ignoreNumericEscapes:false 
+        ignoreNonNumericEscapes:false
+        ignoreSpecialEscapes:false
+        requireParentheses:true
+        ifKeyAbsent:ifNoSuchKeyActionOrNil
+        on:stream.
+    ^ stream contents.
+
+    "
+     'hello %(abc) %1 %a %; %%' expandPlaceholdersWith:nil ifNoSuchKey:[:str :nm | str]
+    "
+!
+
 expandPlaceholdersWith:argArrayOrDictionary on:aStream
     "write the receiver to aStream, where all %i escapes are
      replaced by corresponding arguments' printStrings from the argArrayOrDictionary.
@@ -8563,6 +8818,7 @@
         ignoreNonNumericEscapes:false
         ignoreSpecialEscapes:false
         requireParentheses:true
+        ifKeyAbsent:nil
         on:aStream
 
     "