Wiki

root/cobra/trunk/Source/Utils.cobra

Revision 2657, 9.9 KB (checked in by Chuck.Esterbrook, 9 days ago)

Installer: Build and install the Cobra.Lang.Compiler library by default when installing Cobra.

  • Property svn:eol-style set to native
Line 
1"""
2Miscellaneous utility methods for use throughout the Cobra compiler.
3"""
4
5
6use System.Reflection
7
8
9extend 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
16class 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
61extend 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
123class 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
336class 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
351class NotSupportedException inherits Exception
352
353    cue init
354        base.init
355
356
357class 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
Note: See TracBrowser for help on using the browser.