use System.Text.RegularExpressions namespace Cobra.Core extend System.Object def typeOf as System.Type """ Return the Type of the object this is called on. Using .typeOf is portable between platforms in contrast to CLR .getType and JVM .getClass. """ return .getType def toTechString as String """ Generate a 'technical' string representation of the object suitable for inspection and debugging. Used for AssertExceptions (and subclasses such as RequireException) and the `trace` statement. It provides more information about collections and enumerations and recovers from exceptions when making strings so that debugging can proceed. """ return CobraImp._techStringMaker.makeString(this) def toPrintString as String """ Generate a string representation of the object suitable for printing. Strings are only quoted if they are inside collections such as a list or dictionary. """ return CobraImp._printStringMaker.makeString(this) extend Stack def clone as Stack ensure result is not this result.count == .count result.typeOf is .typeOf body # see: http://stackoverflow.com/questions/7391348/c-sharp-clone-a-stack return Stack(Stack(this)) class TestStack is internal test s = Stack() s.push(1) s.push(2) s.push(3) assert s.toPrintString == '\[3, 2, 1]' c = s.clone assert c is not s assert c.count == s.count assert c.toList == s.toList assert c.toPrintString == s.toPrintString extend String def before(sep as String) as String test s = 'abc|de' assert s.before('|') == 'abc' assert s.before('=') == 'abc|de' assert '|abcde'.before('|') == '' assert 'abcde|'.before('|') == 'abcde' body index = .indexOf(sep) if index < 0, return this return .substring(0, index) def after(sep as String) as String test s = 'abc|de' assert s.after('|') == 'de' assert s.after('=') == '' assert '|abcde'.after('|') == 'abcde' assert 'abcde|'.after('|') == '' body index = .indexOf(sep) if index < 0, return '' return .substring(index+sep.length) def capitalized as String """ Returns the string with the first character capitalized. Returns a blank string for a blank string. """ ensure result.length == .length result.length implies result[0] == result[0].toUpper test assert 'chuck'.capitalized == 'Chuck' assert 'Chuck'.capitalized == 'Chuck' assert ''.capitalized == '' assert ' foo'.capitalized == ' foo' assert 'f'.capitalized == 'F' assert '1aoeu'.capitalized == '1aoeu' body if .length == 0, return this return this[:1].toUpper + this[1:] def count(c as char) as int """ Return a count of the number of occurrences of the char in this string. """ test assert ''.count(c'x')==0 assert 'x'.count(c'x')==1 assert 'X'.count(c'x')==0 # case sensitive assert ' ! ! '.count(c'!')==2 body count = 0 for ch in this, if c == ch, count += 1 return count def count(substring as String) as int """ Return a count of the number of occurrences of the substring in this string. """ test assert ''.count('aoeu') == 0 assert 'x'.count('x') == 1 assert 'xyxy'.count('xy') == 2 assert 'xyxy'.count('a') == 0 body return (.length - .replace(substring, '').length) // substring.length def isCapitalized as bool """ Return a bool indicating if this string is nonzero length and starts with an uppercase letter. """ test assert 'Aoeu'.isCapitalized assert 'Zaoeu'.isCapitalized assert not 'aoeu'.isCapitalized assert not ''.isCapitalized assert not '1234'.isCapitalized body return .length and this[0].isUpper def limitLength(maxLength as int) as String return .limitLength(maxLength, nil) def limitLength(maxLength as int, suffix as String?) as String require suffix implies suffix.length < maxLength ensure result.length <= maxLength test assert ''.limitLength(2) == '' assert 'aoeu'.limitLength(2) == 'ao' assert 'aoeu aoeu aoeu aoeu aoeu'.limitLength(10, '...') == 'aoeu ao...' body if .length <= maxLength, return this return .substring(0, maxLength - (suffix ? '').length) + suffix def md5HashInHex as String """ Return a string of Hex characters of the md5 hash of this string. """ ensure result.length == 32 test assert 'Black holes and revelations.'.md5HashInHex == '95b141d670c19f2f20a820751897b9c6' body md5 = System.Security.Cryptography.MD5CryptoServiceProvider() data = System.Text.Encoding.ascii.getBytes(this) # why ASCII? why not utf8 or something? data = md5.computeHash(data) ret = '' for i in data.length ret += data[i].toString('x2') return ret def repeat(times as int) as String """ Return the string repeated a number of times. """ test assert 'xy'.repeat(3) == 'xyxyxy' assert ''.repeat(1_000_000) == '' body len = .length sb = StringBuilder(len * times) if len, for i in times, sb.append(this) return sb.toString def split(separator as String) as String[] return .split(separator, 2_147_483_647, StringSplitOptions.None) # CC: int.max def split(separator as String, count as int) as String[] require count >= 0 return .split(separator, count, StringSplitOptions.None) def split(separator as String, options as StringSplitOptions) as String[] return .split(separator, 2_147_483_647, options) # CC: int.max def split(separator as String, count as int, options as StringSplitOptions) as String[] """ Return an array of strings created by splitting this string by the given separator up to a maximum of count items, conforming to the given StringSplitOptions. """ require count >= 0 test big = 100 assert ''.split('aoeu', big) == @[''] assert 'aoeu'.split(' ', 0) == @[] assert 'aoeu'.split(' ', big) == @['aoeu'] assert 'aoeuXasdf'.split('X', big) == @['aoeu', 'asdf'] assert 'aoeuXXasdf'.split('XX', big) == @['aoeu', 'asdf'] assert 'aoeuXXasdf'.split('XX', big) == @['aoeu', 'asdf'] assert 'aoeuXXasdf'.split('XX', 0) == @[] # Mono 2.6.x has a bug where Split() can return an extra substring. #assert 'aoeuXXasdf'.split('XX', 1) == ['aoeuXXasdf'] #assert 'aoeuXXasdf'.split('XX', 2) == ['aoeu', 'asdf'] #assert 'aoeuXXasdf'.split('XX', 3) == ['aoeu', 'asdf'] body return .split(@[separator], count, options) to ! def split(chars as List) as List """ Split this string on any of the list of chars given returning a List of Strings. """ test s = 'a,b:c:d,e,f' assert s.split([c',', c':']) == ['a', 'b', 'c', 'd', 'e', 'f'] body return List(.split(chars.toArray) to !) def split(chars as IList) as List """ Split this string on any of the IList of chars given returning a List of Strings. """ charsArray = char[](chars.count) for i in chars.count, charsArray[i] = chars[i] return List(.split(charsArray)) def splitLines as List return .splitLines(false) def splitLines(keepEnds as bool) as List """ Returns the string split into lines, recognizing the various line endings (posix, dos/http, old mac) even if mixed within the same string. If keepEnds is true the nl separators are left on the end of each line. """ test cases = [ ['', false, []], [' ', false, [' ']], ['x', false, ['x']], ['x y', false, ['x y']], ['a\n', false, ['a']], ['a\n', true, ['a\n']], ['a\nb', false, ['a', 'b']], ['a\nb', true, ['a\n', 'b']], ['a\nb\n', false, ['a', 'b']], ['a\nb\n', true, ['a\n', 'b\n']], ['a\r', false, ['a']], ['a\r', true, ['a\r']], ['a\rb', false, ['a', 'b']], ['a\rb', true, ['a\r', 'b']], ['a\rb\r', false, ['a', 'b']], ['a\rb\r', true, ['a\r', 'b\r']], ['a\r\n', false, ['a']], ['a\r\n', true, ['a\r\n']], ['a\r\nb', false, ['a', 'b']], ['a\r\nb', true, ['a\r\n', 'b']], ['a\r\nb\r\n', false, ['a', 'b']], ['a\r\nb\r\n', true, ['a\r\n', 'b\r\n']], ['a\r\n\r\n', false, ['a', '']], ['a\r\n\r\n', true, ['a\r\n', '\r\n']], ['a\r\n\r\n\r\n', false, ['a', '', '']], ['a\r\n\r\n\r\n', true, ['a\r\n', '\r\n', '\r\n']], ['a\rb\nc\r\nd', false, ['a', 'b', 'c', 'd']], ['a\rb\nc\r\nd', true, ['a\r', 'b\n', 'c\r\n', 'd']], ] for a, b, c in cases input = a to String keepEnds = b to bool expected = c # trace input, keepEnds, expected actual = input.splitLines(keepEnds) assert actual == expected body # posix = \n # old mac = \r # dos, http = \r\n lines = List() len = .length i = j = 0 while i < len while i < len c = this[i] if c == c'\n' or c == c'\r', break i += 1 eoli = i if i < len if this[i] == '\r' and i+1 < len and this[i+1] == '\n' i += 2 else i += 1 if keepEnds, eoli = i lines.add(this[j:eoli]) j = i if j < len, lines.add(this[j:]) return lines def splitWords as List """ Split this string into (non-whitespace) words returning them as a list of strings. """ test # preliminary: assert 'foo bar'.split == ['foo', 'bar'] assert 'foo bar'.split == ['foo', '', 'bar'] # undesireable assert 'foo bar'.split == ['foo', '', '', 'bar'] # undesireable assert Regex(r'\s+').split('foo bar\r\nbaz') == ['foo', 'bar', 'baz'] # this method: assert 'foo bar\r\nbaz'.splitWords == ['foo', 'bar', 'baz'] body # to-do: could bypass Regex for better speed return Regex(r'\s+').split(this).toList class DecimalTools # Cobra does not yet support extensions of primitive types # But the compiler will pick up "DecimalTools" as if it were shared def pow(x as decimal, y as decimal) as decimal test assert DecimalTools.pow(1, 1) == 1 assert DecimalTools.pow(1, 2) == 1 assert DecimalTools.pow(2, 2) == 4 assert DecimalTools.pow(2, 3) == 8 assert DecimalTools.pow(3, 3) == 27 assert DecimalTools.pow(3, 4.5d).round(3) == 140.296d body if y.remainder(1.0d) == 0 and y > 0 and y <= Int32.maxValue # a round power like 5.0 r = x for i in (y to int)-1, r *= x return r else # would be nice to have a real algorithm for this to avoid the conversion to float # which has a smaller range, fewer significant digits and lossy representation of # some of decimal's values return (x to float).pow(y to float) to decimal def sqrt(x as decimal) as decimal test assert DecimalTools.sqrt(1) == 1 assert DecimalTools.sqrt(2).round(3) == 1.414d assert DecimalTools.sqrt(3).round(3) == 1.732d assert DecimalTools.sqrt(4) == 2 assert DecimalTools.sqrt(4.5d).round(3) == 2.121d expect OverflowException, DecimalTools.sqrt(-4.5d) body # would be nice to have a real algorithm for this to avoid the conversion to float # which has a smaller range, fewer significant digits and lossy representation of # some of decimal's values return (x to float).sqrt to decimal