Wiki

root/cobra/trunk/Source/Cobra.Lang/Extensions.cobra

Revision 2585, 10.1 KB (checked in by Charles.Esterbrook, 10 months ago)

Added String.before and .after extension methods.
credit:hopscc

  • Property svn:eol-style set to native
Line 
1use System.Text.RegularExpressions
2
3
4namespace Cobra.Lang
5
6    extend System.Object
7
8        def typeOf as System.Type
9            """
10            Return the Type of the object this is called on.
11            Using .typeOf is portable between platforms in contrast to CLR .getType and JVM .getClass.
12            """
13            return .getType
14
15        def toTechString as String
16            """
17            Generate a 'technical' string representation of the object suitable for inspection and debugging.
18            Used for AssertExceptions (and subclasses such as RequireException) and the `trace` statement.
19            It provides more information about collections and enumerations and recovers from exceptions
20            when making strings so that debugging can proceed.
21            """
22            return CobraImp._techStringMaker.makeString(this)
23
24        def toPrintString as String
25            """
26            Generate a string representation of the object suitable for printing.
27            Strings are only quoted if they are inside collections such as a list or dictionary.
28            """     
29            return CobraImp._printStringMaker.makeString(this)
30
31
32    extend String
33   
34        def before(sep as String) as String
35            test
36                s = 'abc|de'
37                assert s.before('|') == 'abc'
38                assert s.before('=') == 'abc|de'
39                assert '|abcde'.before('|') == ''
40                assert 'abcde|'.before('|') == 'abcde'
41            body
42                index = .indexOf(sep)
43                if index < 0, return this
44                return .substring(0, index)
45
46        def after(sep as String) as String
47            test
48                s = 'abc|de'
49                assert s.after('|') == 'de'
50                assert s.after('=') == ''
51                assert '|abcde'.after('|') == 'abcde'
52                assert 'abcde|'.after('|') == ''
53            body
54                index = .indexOf(sep)
55                if index < 0, return ''
56                return .substring(index+sep.length)
57                               
58        def capitalized as String
59            """
60            Returns the string with the first character capitalized.
61            Returns a blank string for a blank string.
62            """
63            ensure
64                result.length == .length
65                result.length implies result[0] == result[0].toUpper
66            test
67                assert 'chuck'.capitalized == 'Chuck'
68                assert 'Chuck'.capitalized == 'Chuck'
69                assert ''.capitalized == ''
70                assert ' foo'.capitalized == ' foo'
71                assert 'f'.capitalized == 'F'
72                assert '1aoeu'.capitalized == '1aoeu'
73            body
74                if .length == 0, return this
75                return this[:1].toUpper + this[1:]
76
77        def count(c as char) as int
78            """ Return a count of the number of occurrences of the char in this string. """
79            test
80                assert ''.count(c'x')==0
81                assert 'x'.count(c'x')==1
82                assert 'X'.count(c'x')==0  # case sensitive
83                assert ' ! ! '.count(c'!')==2
84            body
85                count = 0
86                for ch in this, if c == ch, count += 1
87                return count
88
89        def count(substring as String) as int
90            """ Return a count of the number of occurrences of the substring in this string. """
91            test
92                assert ''.count('aoeu') == 0
93                assert 'x'.count('x') == 1
94                assert 'xyxy'.count('xy') == 2
95                assert 'xyxy'.count('a') == 0
96            body
97                return (.length - .replace(substring, '').length) // substring.length
98
99        def isCapitalized as bool
100            """ Return a bool indicating if this string is nonzero length and starts with an uppercase letter. """
101            test
102                assert 'Aoeu'.isCapitalized
103                assert 'Zaoeu'.isCapitalized
104                assert not 'aoeu'.isCapitalized
105                assert not ''.isCapitalized
106                assert not '1234'.isCapitalized
107            body
108                return .length and this[0].isUpper
109
110        def md5HashInHex as String
111            """ Return a string of Hex characters of the md5 hash of this string. """
112            ensure
113                result.length == 32
114            test
115                assert 'Black holes and revelations.'.md5HashInHex == '95b141d670c19f2f20a820751897b9c6'
116            body
117                md5 = System.Security.Cryptography.MD5CryptoServiceProvider()
118                data = System.Text.Encoding.ascii.getBytes(this# why ASCII? why not utf8 or something?
119                data = md5.computeHash(data)
120                ret = ''
121                for i in data.length
122                    ret += data[i].toString('x2')
123                return ret
124
125        def repeat(times as int) as String
126            """
127            Return the string repeated a number of times.
128            """
129            test
130                assert 'xy'.repeat(3) == 'xyxyxy'
131                assert ''.repeat(1_000_000) == ''
132            body
133                len = .length
134                sb = StringBuilder(len * times)
135                if len, for i in times, sb.append(this)
136                return sb.toString
137
138        def split(separator as String) as String[]
139            return .split(separator, 2_147_483_647, StringSplitOptions.None)  # CC: int.max
140
141        def split(separator as String, count as int) as String[]
142            require count >= 0
143            return .split(separator, count, StringSplitOptions.None)
144
145        def split(separator as String, options as StringSplitOptions) as String[]
146            return .split(separator, 2_147_483_647, options)  # CC: int.max
147
148        def split(separator as String, count as int, options as StringSplitOptions) as String[]
149            """
150            Return an array of strings created by splitting this string by the given separator
151            up to a maximum of count items, conforming to the given StringSplitOptions.
152            """
153            require
154                count >= 0
155            test
156                big = 100
157                assert ''.split('aoeu', big) == @['']
158                assert 'aoeu'.split(' ', 0) == @[]
159                assert 'aoeu'.split(' ', big) == @['aoeu']
160                assert 'aoeuXasdf'.split('X', big) == @['aoeu', 'asdf']
161                assert 'aoeuXXasdf'.split('XX', big) == @['aoeu', 'asdf']
162                assert 'aoeuXXasdf'.split('XX', big) == @['aoeu', 'asdf']
163                assert 'aoeuXXasdf'.split('XX', 0) == @[]
164                # Mono 2.6.x has a bug where Split() can return an extra substring.
165                #assert 'aoeuXXasdf'.split('XX', 1) == ['aoeuXXasdf']
166                #assert 'aoeuXXasdf'.split('XX', 2) == ['aoeu', 'asdf']
167                #assert 'aoeuXXasdf'.split('XX', 3) == ['aoeu', 'asdf']
168            body
169                return .split(@[separator], count, options) to !
170
171        def split(chars as List<of char>) as List<of String>
172            """ Split this string on any of the list of chars given returning a List of Strings. """
173            test
174                s = 'a,b:c:d,e,f'
175                assert s.split([c',', c':']) == ['a', 'b', 'c', 'd', 'e', 'f']
176            body
177                return List<of String>(.split(chars.toArray) to !)
178   
179        def split(chars as IList<of char>) as List<of String>
180            """ Split this string on any of the IList of chars given returning a List of Strings. """
181            charsArray = char[](chars.count)
182            for i in chars.count, charsArray[i] = chars[i]
183            return List<of String>(.split(charsArray))
184
185        def splitLines as List<of String>
186            return .splitLines(false)
187       
188        def splitLines(keepEnds as bool) as List<of String>
189            """
190            Returns the string split into lines, recognizing the various line endings (posix, dos/http, old mac) even if mixed within the same string.
191            If keepEnds is true the nl separators are left on the end of each line.
192            """
193            test
194                cases = [
195                    ['', false, []],
196                    ['   ', false, ['   ']],
197                    ['x', false, ['x']],
198                    ['x y', false, ['x y']],
199
200                    ['a\n', false, ['a']],
201                    ['a\n', true, ['a\n']],
202                    ['a\nb', false, ['a', 'b']],
203                    ['a\nb', true, ['a\n', 'b']],
204                    ['a\nb\n', false, ['a', 'b']],
205                    ['a\nb\n', true, ['a\n', 'b\n']],
206
207                    ['a\r', false, ['a']],
208                    ['a\r', true, ['a\r']],
209                    ['a\rb', false, ['a', 'b']],
210                    ['a\rb', true, ['a\r', 'b']],
211                    ['a\rb\r', false, ['a', 'b']],
212                    ['a\rb\r', true, ['a\r', 'b\r']],
213
214                    ['a\r\n', false, ['a']],
215                    ['a\r\n', true, ['a\r\n']],
216                    ['a\r\nb', false, ['a', 'b']],
217                    ['a\r\nb', true, ['a\r\n', 'b']],
218                    ['a\r\nb\r\n', false, ['a', 'b']],
219                    ['a\r\nb\r\n', true, ['a\r\n', 'b\r\n']],
220               
221                    ['a\r\n\r\n', false, ['a', '']],
222                    ['a\r\n\r\n', true, ['a\r\n', '\r\n']],
223                    ['a\r\n\r\n\r\n', false, ['a', '', '']],
224                    ['a\r\n\r\n\r\n', true, ['a\r\n', '\r\n', '\r\n']],
225               
226                    ['a\rb\nc\r\nd', false, ['a', 'b', 'c', 'd']],
227                    ['a\rb\nc\r\nd', true, ['a\r', 'b\n', 'c\r\n', 'd']],
228                ]
229                for a, b, c in cases
230                    input = a to String
231                    keepEnds = b to bool
232                    expected = c
233                    # trace input, keepEnds, expected
234                    actual = input.splitLines(keepEnds)
235                    assert actual == expected
236            body
237                # posix = \n
238                # old mac = \r
239                # dos, http = \r\n
240                lines = List<of String>()
241                len = .length
242                i = j = 0
243                while i < len
244                    while i < len
245                        c = this[i]
246                        if c == c'\n' or c == c'\r', break
247                        i += 1
248                    eoli = i
249                    if i < len
250                        if this[i] == '\r' and i+1 < len and this[i+1] == '\n'
251                            i += 2
252                        else
253                            i += 1
254                        if keepEnds, eoli = i
255                    lines.add(this[j:eoli])
256                    j = i
257                if j < len, lines.add(this[j:])
258                return lines
259
260        def splitWords as List<of String>
261            """ Split this string into (non-whitespace) words returning them as a list of strings. """
262            test
263                # preliminary:
264                assert 'foo bar'.split == ['foo', 'bar']
265                assert 'foo  bar'.split == ['foo', '', 'bar']  # undesireable
266                assert 'foo   bar'.split == ['foo', '', '', 'bar']  # undesireable
267                assert Regex(r'\s+').split('foo  bar\r\nbaz') == ['foo', 'bar', 'baz']
268                # this method:
269                assert 'foo  bar\r\nbaz'.splitWords == ['foo', 'bar', 'baz']
270            body
271                # to-do: could bypass Regex for better speed
272                return Regex(r'\s+').split(this).toList
273
274   
275    class DecimalTools
276
277        # Cobra does not yet support extensions of primitive types
278        # But the compiler will pick up "DecimalTools" as if it were
279   
280        shared
281   
282            def pow(x as decimal, y as decimal) as decimal
283                test
284                    assert DecimalTools.pow(1, 1) == 1
285                    assert DecimalTools.pow(1, 2) == 1
286                    assert DecimalTools.pow(2, 2) == 4
287                    assert DecimalTools.pow(2, 3) == 8
288                    assert DecimalTools.pow(3, 3) == 27
289                    assert DecimalTools.pow(3, 4.5d).round(3) == 140.296d
290                body
291                    if y.remainder(1.0d) == 0 and y > 0 and y <= Int32.maxValue
292                        # a round power like 5.0
293                        r = x
294                        for i in (y to int)-1, r *= x
295                        return r
296                    else
297                        # would be nice to have a real algorithm for this to avoid the conversion to float
298                        # which has a smaller range, fewer significant digits and lossy representation of
299                        # some of decimal's values
300                        return (x to float).pow(y to float) to decimal
301
302            def sqrt(x as decimal) as decimal
303                test
304                    assert DecimalTools.sqrt(1) == 1
305                    assert DecimalTools.sqrt(2).round(3) == 1.414d
306                    assert DecimalTools.sqrt(3).round(3) == 1.732d
307                    assert DecimalTools.sqrt(4) == 2
308                    assert DecimalTools.sqrt(4.5d).round(3) == 2.121d
309                    expect OverflowException, DecimalTools.sqrt(-4.5d)
310                body
311                    # would be nice to have a real algorithm for this to avoid the conversion to float
312                    # which has a smaller range, fewer significant digits and lossy representation of
313                    # some of decimal's values
314                    return (x to float).sqrt to decimal
Note: See TracBrowser for help on using the browser.