| 1 | """ |
|---|
| 2 | Miscellaneous utility methods for use throughout the Cobra compiler. |
|---|
| 3 | """ |
|---|
| 4 | |
|---|
| 5 | |
|---|
| 6 | use System.Reflection |
|---|
| 7 | |
|---|
| 8 | |
|---|
| 9 | extend IList<of T> |
|---|
| 10 | |
|---|
| 11 | def toList2 as List<of T> |
|---|
| 12 | """ Turn an IEnumerable of T into a list of T. """ |
|---|
| 13 | return List<of T>(this) |
|---|
| 14 | |
|---|
| 15 | |
|---|
| 16 | class ObjectLister |
|---|
| 17 | |
|---|
| 18 | shared |
|---|
| 19 | |
|---|
| 20 | def list(obj) |
|---|
| 21 | print |
|---|
| 22 | print 'Object:', obj |
|---|
| 23 | print ' getType = [obj.getType.name]' |
|---|
| 24 | props = List<of PropertyInfo>(obj.getType.getProperties) |
|---|
| 25 | props.sort(do(a as PropertyInfo, b as PropertyInfo)=a.name.toLower.compareTo(b.name.toLower)) |
|---|
| 26 | for pi in props |
|---|
| 27 | if pi.canRead |
|---|
| 28 | try |
|---|
| 29 | value = pi.getValue(obj, nil) |
|---|
| 30 | catch TargetParameterCountException |
|---|
| 31 | # happens for .Item[i] "indexer" |
|---|
| 32 | continue |
|---|
| 33 | catch exc as Exception |
|---|
| 34 | try |
|---|
| 35 | if exc inherits TargetInvocationException and exc.innerException |
|---|
| 36 | exc = exc.innerException to ! |
|---|
| 37 | value = '(exception: [exc.getType.name]: [exc.message])' |
|---|
| 38 | catch |
|---|
| 39 | value = '(exception)' |
|---|
| 40 | success |
|---|
| 41 | value = _toTechString(value) |
|---|
| 42 | name = pi.name |
|---|
| 43 | name = Utils.cobraNameForNativeMemberName(name) |
|---|
| 44 | print ' [name] = [value]' |
|---|
| 45 | if obj inherits System.Collections.ICollection |
|---|
| 46 | i = 0 |
|---|
| 47 | for item in obj |
|---|
| 48 | print ' \[[i]] = [_toTechString(item)]' |
|---|
| 49 | i += 1 |
|---|
| 50 | |
|---|
| 51 | def _toTechString(value) as String |
|---|
| 52 | try |
|---|
| 53 | return CobraCore.toTechString(value) |
|---|
| 54 | catch exc as Exception |
|---|
| 55 | try |
|---|
| 56 | return '(exception: [exc.getType.name]: [exc.message])' |
|---|
| 57 | catch |
|---|
| 58 | return '(exception from .toTechString)' |
|---|
| 59 | |
|---|
| 60 | |
|---|
| 61 | extend String |
|---|
| 62 | |
|---|
| 63 | def before(sep as String) as String |
|---|
| 64 | test |
|---|
| 65 | s = 'abc|de' |
|---|
| 66 | assert s.before('|') == 'abc' |
|---|
| 67 | assert s.before('=') == 'abc|de' |
|---|
| 68 | assert '|abcde'.before('|') == '' |
|---|
| 69 | assert 'abcde|'.before('|') == 'abcde' |
|---|
| 70 | body |
|---|
| 71 | index = .indexOf(sep) |
|---|
| 72 | if index < 0, return this |
|---|
| 73 | return .substring(0, index) |
|---|
| 74 | |
|---|
| 75 | def after(sep as String) as String |
|---|
| 76 | test |
|---|
| 77 | s = 'abc|de' |
|---|
| 78 | assert s.after('|') == 'de' |
|---|
| 79 | assert s.after('=') == '' |
|---|
| 80 | assert '|abcde'.after('|') == 'abcde' |
|---|
| 81 | assert 'abcde|'.after('|') == '' |
|---|
| 82 | body |
|---|
| 83 | index = .indexOf(sep) |
|---|
| 84 | if index < 0, return '' |
|---|
| 85 | return .substring(index+sep.length) |
|---|
| 86 | |
|---|
| 87 | ## Language specific |
|---|
| 88 | def canBeUndottedMemberName as bool |
|---|
| 89 | """ |
|---|
| 90 | Returns true if this string is the kind of name that can reference a box member without |
|---|
| 91 | using the dot operator. |
|---|
| 92 | |
|---|
| 93 | Returns true if the name starts with an underscore or capital letter. The underscored |
|---|
| 94 | names are typically protected data fields or methods while the uppercase names would be |
|---|
| 95 | enums or (in the future) nested boxes. |
|---|
| 96 | """ |
|---|
| 97 | return .startsWith('_') or .isCapitalized |
|---|
| 98 | |
|---|
| 99 | def startsWithLowerLetter as bool |
|---|
| 100 | require |
|---|
| 101 | .length |
|---|
| 102 | test |
|---|
| 103 | assert 'a'.startsWithLowerLetter |
|---|
| 104 | assert 'z'.startsWithLowerLetter |
|---|
| 105 | assert not 'A'.startsWithLowerLetter |
|---|
| 106 | assert not '1'.startsWithLowerLetter |
|---|
| 107 | assert not '_'.startsWithLowerLetter |
|---|
| 108 | body |
|---|
| 109 | return this[0].isLower |
|---|
| 110 | |
|---|
| 111 | def startsWithNonLowerLetter as bool |
|---|
| 112 | require |
|---|
| 113 | .length |
|---|
| 114 | test |
|---|
| 115 | assert not 'a'.startsWithNonLowerLetter |
|---|
| 116 | assert not 'z'.startsWithNonLowerLetter |
|---|
| 117 | assert not '1'.startsWithNonLowerLetter |
|---|
| 118 | assert 'A'.startsWithNonLowerLetter |
|---|
| 119 | body |
|---|
| 120 | return this[0] <> this[0].toLower |
|---|
| 121 | |
|---|
| 122 | |
|---|
| 123 | class Utils is partial |
|---|
| 124 | |
|---|
| 125 | shared |
|---|
| 126 | |
|---|
| 127 | def combinePaths(a as String, b as String) as String |
|---|
| 128 | """ |
|---|
| 129 | Same as Path.combine() but leaves no '\.\' or '/./' in the result. |
|---|
| 130 | """ |
|---|
| 131 | p = Path.combine(a, b) |
|---|
| 132 | good = Path.directorySeparatorChar.toString |
|---|
| 133 | bad = '[good].[good]' |
|---|
| 134 | p = p.replace(bad, good) |
|---|
| 135 | return p |
|---|
| 136 | |
|---|
| 137 | def normalizePath(path as String) as String |
|---|
| 138 | while path.startsWith('.\\'), path = path[2:] |
|---|
| 139 | while path.startsWith('./'), path = path[2:] |
|---|
| 140 | return path |
|---|
| 141 | |
|---|
| 142 | def plural(items) as String |
|---|
| 143 | ensure |
|---|
| 144 | result == '' or result == 's' |
|---|
| 145 | test |
|---|
| 146 | assert Utils.plural([]) == 's' |
|---|
| 147 | assert Utils.plural([2]) == '' |
|---|
| 148 | assert Utils.plural([2, 2]) == 's' |
|---|
| 149 | body |
|---|
| 150 | return if(items.count==1, '', 's') |
|---|
| 151 | |
|---|
| 152 | def pluralize(name as String) as String |
|---|
| 153 | test |
|---|
| 154 | assert Utils.pluralize('bar') == 'bars' |
|---|
| 155 | assert Utils.pluralize('class') == 'classes' |
|---|
| 156 | body |
|---|
| 157 | return name + if(name.endsWith('s'), 'es', 's') |
|---|
| 158 | |
|---|
| 159 | def toIdentifier(s as String) as String |
|---|
| 160 | test |
|---|
| 161 | cases = [ |
|---|
| 162 | ['', ''], |
|---|
| 163 | ['aoeu', 'aoeu'], |
|---|
| 164 | [' aoeu', '_aoeu'], |
|---|
| 165 | ['aoeu/aoeu', 'aoeu_aoeu'], |
|---|
| 166 | ] |
|---|
| 167 | for case in cases |
|---|
| 168 | assert Utils.toIdentifier(case[0]) == case[1] |
|---|
| 169 | body |
|---|
| 170 | sb = StringBuilder() |
|---|
| 171 | for c in s |
|---|
| 172 | if c.isLetterOrDigit or c == c'_' |
|---|
| 173 | sb.append(c) |
|---|
| 174 | else |
|---|
| 175 | sb.append(c'_') |
|---|
| 176 | return sb.toString |
|---|
| 177 | |
|---|
| 178 | |
|---|
| 179 | ## BackEnd C# |
|---|
| 180 | |
|---|
| 181 | def cobraNameForNativeMemberName(name as String) as String |
|---|
| 182 | require |
|---|
| 183 | name.length |
|---|
| 184 | ensure |
|---|
| 185 | not result[0].isUpper |
|---|
| 186 | test |
|---|
| 187 | cases = [ |
|---|
| 188 | 'Foo foo', |
|---|
| 189 | 'FooBar fooBar', |
|---|
| 190 | 'PI pi', |
|---|
| 191 | 'OSVersion osVersion', |
|---|
| 192 | 'XMLParser xmlParser', |
|---|
| 193 | 'foo foo', |
|---|
| 194 | 'fooBar fooBar', |
|---|
| 195 | '_foo _foo', |
|---|
| 196 | '_fooBar _fooBar', |
|---|
| 197 | '_Foo _Foo', |
|---|
| 198 | '_FooBar _FooBar', |
|---|
| 199 | '_ _', |
|---|
| 200 | 'UTF8 utf8', |
|---|
| 201 | ] |
|---|
| 202 | for case in cases |
|---|
| 203 | parts = case.split |
|---|
| 204 | assert Utils.cobraNameForNativeMemberName(parts[0]) == parts[1] |
|---|
| 205 | body |
|---|
| 206 | if name.length == 1 |
|---|
| 207 | return name.toLower |
|---|
| 208 | else |
|---|
| 209 | upperCount = 0 |
|---|
| 210 | for ch in name |
|---|
| 211 | if ch.isUpper |
|---|
| 212 | upperCount += 1 |
|---|
| 213 | else |
|---|
| 214 | break |
|---|
| 215 | if upperCount == 0 |
|---|
| 216 | return name |
|---|
| 217 | if upperCount == name.length # all upper like 'PI' |
|---|
| 218 | return name.toLower |
|---|
| 219 | if upperCount == 1 |
|---|
| 220 | return name[0].toLower.toString + name[1:] |
|---|
| 221 | else |
|---|
| 222 | # multi upper count like OSVersion or UTF8 |
|---|
| 223 | if name[upperCount].isLetter |
|---|
| 224 | return name[:upperCount-1].toLower + name[upperCount-1:] # osVersion |
|---|
| 225 | else |
|---|
| 226 | return name[:upperCount].toLower + name[upperCount:] # utf8 |
|---|
| 227 | |
|---|
| 228 | def sharpStringLiteralFor(s as String) as String |
|---|
| 229 | # Reference: "Escape Sequences" - http://msdn.microsoft.com/en-us/library/h21280bw.aspx |
|---|
| 230 | sb = StringBuilder() |
|---|
| 231 | sb.append('"') |
|---|
| 232 | for c in s |
|---|
| 233 | branch c |
|---|
| 234 | on c'\a', sb.append('\\a') |
|---|
| 235 | on c'\b', sb.append('\\b') |
|---|
| 236 | on c'\f', sb.append('\\f') |
|---|
| 237 | on c'\n', sb.append('\\n') |
|---|
| 238 | on c'\r', sb.append('\\r') |
|---|
| 239 | on c'\t', sb.append('\\t') |
|---|
| 240 | on c'\v', sb.append('\\v') |
|---|
| 241 | on c'"', sb.append('\\"') |
|---|
| 242 | on c'\\', sb.append('\\\\') |
|---|
| 243 | on c'\0', sb.append('\\0') |
|---|
| 244 | else, sb.append(c) |
|---|
| 245 | sb.append('"') |
|---|
| 246 | return sb.toString |
|---|
| 247 | |
|---|
| 248 | def printSource(src as String) |
|---|
| 249 | lineNum = 1 |
|---|
| 250 | for line in src.split(c'\n') |
|---|
| 251 | line = line.replace('\t', ' ') |
|---|
| 252 | print '[lineNum]|[line]' # CC: right align and pad 0 the lineNum |
|---|
| 253 | lineNum += 1 |
|---|
| 254 | |
|---|
| 255 | def forceExtension(fileName as String, extension as String) as String |
|---|
| 256 | require |
|---|
| 257 | fileName.length |
|---|
| 258 | extension.length |
|---|
| 259 | ensure |
|---|
| 260 | result.endsWith(extension) |
|---|
| 261 | test |
|---|
| 262 | assert Utils.forceExtension('foo.csv', '.exe') == 'foo.exe' |
|---|
| 263 | assert Utils.forceExtension('foo.csv', 'exe') == 'foo.exe' |
|---|
| 264 | assert Utils.forceExtension('foo', '.exe') == 'foo.exe' |
|---|
| 265 | assert Utils.forceExtension('foo', 'exe') == 'foo.exe' |
|---|
| 266 | assert Utils.forceExtension('foo.bar.csv', '.exe') == 'foo.bar.exe' |
|---|
| 267 | body |
|---|
| 268 | if not extension.startsWith('.') |
|---|
| 269 | extension = '.' + extension |
|---|
| 270 | return Path.changeExtension(fileName, extension) to ! |
|---|
| 271 | |
|---|
| 272 | get isDevMachine as bool |
|---|
| 273 | return Environment.getEnvironmentVariable('COBRA_IS_DEV_MACHINE') == '1' |
|---|
| 274 | |
|---|
| 275 | get isRunningOnUnix as bool |
|---|
| 276 | """ |
|---|
| 277 | Returns true if the current process is running on Unix/Posix/Linux/BSD/etc. |
|---|
| 278 | TODO: move to CobraCore |
|---|
| 279 | """ |
|---|
| 280 | platform = Environment.osVersion.platform to int |
|---|
| 281 | return platform in [4, 6, 128] # http://www.mono-project.com/FAQ:_Technical |
|---|
| 282 | |
|---|
| 283 | def readKeyValues(path as String) as Dictionary<of String, String> |
|---|
| 284 | require path.length |
|---|
| 285 | return .readKeyValues(path, File.openText(path), Console.out) |
|---|
| 286 | |
|---|
| 287 | def readKeyValues(path as String, tr as TextReader, warnings as TextWriter) as Dictionary<of String, String> |
|---|
| 288 | test |
|---|
| 289 | nl = Environment.newLine |
|---|
| 290 | warnings = Console.out |
|---|
| 291 | d = Utils.readKeyValues('test', StringReader('# blah[nl][nl]foo = bar[nl]'), warnings) |
|---|
| 292 | assert d.count == 1 |
|---|
| 293 | assert d['foo'] == 'bar' |
|---|
| 294 | body |
|---|
| 295 | d = Dictionary<of String, String>() |
|---|
| 296 | while true |
|---|
| 297 | line = tr.readLine |
|---|
| 298 | if line is nil, break |
|---|
| 299 | line = line.trim |
|---|
| 300 | if line == '' or line.startsWith('#'), continue |
|---|
| 301 | parts = line.split(@[c'='], 2) |
|---|
| 302 | if parts.length == 1 |
|---|
| 303 | warnings.writeLine('.readKeyValue: [path]: ignoring line: [line]') # TODO: report line number |
|---|
| 304 | continue |
|---|
| 305 | key, value = parts[0].trim, parts[1].trim |
|---|
| 306 | d[key] = value |
|---|
| 307 | return d |
|---|
| 308 | |
|---|
| 309 | var _cultureInfoForNumbers = System.Globalization.CultureInfo('en-US', false) |
|---|
| 310 | |
|---|
| 311 | get cultureInfoForNumbers from var |
|---|
| 312 | """ |
|---|
| 313 | To control decimal point and thousands separators in the language, it is necessary to |
|---|
| 314 | pass a CultureInfo to .parse and .toString methods of decimal and float. Without this, |
|---|
| 315 | Cobra would expect numeric literals to be formatted to the culture of the current user. |
|---|
| 316 | This makes source code non-portable. This problem was reported from a non-English |
|---|
| 317 | culture and the report requested that Cobra consistently use '.' for decimal. |
|---|
| 318 | """ |
|---|
| 319 | |
|---|
| 320 | def loadWithPartialName(name as String) as Assembly? |
|---|
| 321 | # TODO: Holy crap! loadWithPartialName is marked obsolete, |
|---|
| 322 | # but I know of no other way to support a library reference like "compiler /r:System.Data.dll myprog.ext" as C#, Cobra, etc. do. |
|---|
| 323 | # Suggestions/solutions are welcome. |
|---|
| 324 | |
|---|
| 325 | # avoid warning: "System.Reflection.Assembly.LoadWithPartialName(string)" is obsolete |
|---|
| 326 | # return Assembly.loadWithPartialName(name) |
|---|
| 327 | # with dynamic binding: |
|---|
| 328 | assemblyType = Assembly to dynamic |
|---|
| 329 | try |
|---|
| 330 | ass = assemblyType.loadWithPartialName(name) |
|---|
| 331 | catch System.Reflection.TargetInvocationException # or any Exception |
|---|
| 332 | ass = nil |
|---|
| 333 | return ass |
|---|
| 334 | |
|---|
| 335 | |
|---|
| 336 | class ShouldNotCallException inherits Exception |
|---|
| 337 | # TODO: perhaps there should also be an attribute so the compiler can give compile-time warnings |
|---|
| 338 | |
|---|
| 339 | var _type as Type? |
|---|
| 340 | |
|---|
| 341 | cue init |
|---|
| 342 | base.init |
|---|
| 343 | |
|---|
| 344 | cue init(t as Type) |
|---|
| 345 | base.init('Type is [t]') |
|---|
| 346 | _type = t |
|---|
| 347 | |
|---|
| 348 | get containingType from _type |
|---|
| 349 | |
|---|
| 350 | |
|---|
| 351 | class NotSupportedException inherits Exception |
|---|
| 352 | |
|---|
| 353 | cue init |
|---|
| 354 | base.init |
|---|
| 355 | |
|---|
| 356 | |
|---|
| 357 | class BasicTests |
|---|
| 358 | |
|---|
| 359 | test |
|---|
| 360 | # verify sanity |
|---|
| 361 | assert 2 + 2 == 4 |
|---|
| 362 | |
|---|
| 363 | # verify that .toList produces a new list when used on lists |
|---|
| 364 | t = [1, 2, 3] |
|---|
| 365 | assert t.toList is not t |
|---|