Wiki

root/cobra/trunk/Source/CommandLine.cobra

Revision 2658, 51.0 KB (checked in by Charles.Esterbrook, 9 days ago)

For "cobra -about", show the path that cobra.exe comes from.

  • Property svn:eol-style set to native
Line 
1"""
2Cobra Command Line Program (compiler and more)
3"""
4
5use System.Diagnostics
6use System.Reflection
7use System.Text.RegularExpressions
8
9
10class OptionSpec inherits Dictionary<of String, dynamic>
11    """
12    Represents a specific, named command line option specification.
13
14    In addition to being a dictionary containing the original raw specification key+value pairs,
15    several convenient properties and methods are provided such as .name, .type and .hasDefault.
16    """
17
18    var _isUnpacked = false 
19    var _name = ''
20    var _type = 'unknown'
21    var _synonyms = List<of String>()
22    var _description = 'No description.'
23    var _isAccumulator = false
24    var _hasDefault = false
25    var _default = ''
26    var _choices = List<of String>()
27
28    get isUnpacked from var
29
30    get name as String
31        require .isUnpacked
32        return _name
33
34    get isMain from var as bool
35   
36    get type as String
37        require .isUnpacked
38        ensure result in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string']
39        return _type
40
41    get synonyms from var
42
43    get description from var
44
45    get isAccumulator from var
46   
47    get hasDefault from var
48   
49    get default from var
50        """
51        Check .hasDefault before using this property for anything useful.
52        """
53
54    get choices from var
55        """
56        The choices for options that are type 'menu'.
57        """
58           
59    def isOptionSpecRestrictionViolated as bool
60        """
61        Returns true if the option spec has a 'restriction' key and the check against that restriction is true.
62        """
63        if .containsKey('restriction')
64            branch this['restriction'] to String
65                on 'mono-only'
66                    return not CobraCore.isRunningOnMono
67        return false
68
69    def unpack
70        _name = this['name'] to String
71        try
72            _type = this['type'] to String
73        catch KeyNotFoundException
74            if .containsKey('isAccumulator') and this['isAccumulator'] to bool
75                this['type'] = _type = 'accumulator'
76            else
77                this['type'] = 'string'
78        if _type == 'main'
79            _isMain, _type = true, 'bool'
80        if .containsKey('is-main'), _isMain = this['is-main'] to bool
81        assert _type in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string']
82        if .containsKey('synonyms')
83            for syn in this['synonyms']
84                _synonyms.add(syn)
85        if .containsKey('description')
86            _description = this['description']
87        if .containsKey('isAccumulator')
88            _isAccumulator = this['isAccumulator'] to bool
89        if .containsKey('default')
90            _default = this['default']
91            _hasDefault = true
92        if .containsKey('choices')
93            for choice in this['choices']
94                _choices.add(choice)
95        _unpackPlatforms
96        _isUnpacked = true
97        assert .type=='menu' implies .choices.count > 0
98        assert .choices.count <> 0 implies .type in ['menu', 'set']
99
100    def _unpackPlatforms
101        if not .containsKey('platforms')
102            this['platforms'] = ['jvm', '.net', 'objc']
103        for platform in this['platforms']
104            assert platform in ['jvm', '.net', 'objc']
105        this['platforms'] = Set<of String>(this['platforms'] to List<of String>)
106
107
108class CommandLineOptionSpecs
109    """
110    Returns the command line option specifications, via .specsList, as a List of OptionSpec.
111    Also, the raw option specifications are contained and maintained in this class.
112    """
113
114    var _specs = List<of OptionSpec>()
115
116    cue init
117        base.init
118        for rawSpec in _rawCommandLineOptionSpecs
119            spec = OptionSpec()
120            if rawSpec inherits Dictionary<of String, Object>
121                for key in rawSpec.keys, spec[key] = rawSpec[key]
122            else if rawSpec inherits Dictionary<of String, String>
123                for key in rawSpec.keys, spec[key] = rawSpec[key]
124            else
125                throw FallThroughException(rawSpec.getType)
126            spec.unpack
127            _specs.add(spec)
128
129    def specsList as List<of OptionSpec>
130        """
131        Returns the option specs in the order they were declared.
132        """
133        return _specs
134
135    var _rawCommandLineOptionSpecs = [
136        {
137            'name': 'about',
138            'description': 'Print the name, copyright, etc. but no usage.',
139            'type': 'main',
140        },
141        {
142            'name': 'back-end',
143            'description': 'Specify the back-end of the compiler, possibly different than the build platform. Used for cross-compilation.',
144            'type': 'menu',
145            'choices': ['none', 'clr', 'jvm', 'objc'],
146            'args': 'none|clr|jvm|objc',
147            'default': 'none',
148        },
149        {
150            'name': 'build-standard-library',
151            'synonyms': ['bsl'],
152            'description': 'Builds the standard library.',
153            'type': 'main',
154            'developer-only': true,
155        },
156        {
157            'name': 'compile',
158            'synonyms': ['c'],
159            'description': 'Compile the library (to DLL) or the program (to EXE) without running the code.',
160            'type': 'main',
161        },
162        {
163            'name': 'compile-if-needed',
164            'synonyms': ['cin'],
165            'type': 'bool',
166            'description': 'Compile if any source file is newer than the existing output file (or there is no output file). Does not consider changes in compiler flags; only source code.',
167        },
168        {
169            'name': 'color',
170            'type': 'bool',
171            'description': 'Colorizes the output of error messages and the messages "Compilation failed" and "Compilation succeeded" (as red, red and blue).',
172        },
173        {
174            'name': 'contracts',
175            'description': 'Control treatment of code generation for contracts.',
176            'type': 'menu',
177            'choices': ['none', 'inline', 'methods'],
178            'args': 'none|inline|methods',
179            'default': 'inline',
180        },
181        {
182            'name': 'correct-source',
183            'synonyms': ['cs'],
184            'description': 'Rewrite source files with corrections, only when unambiguous. For example, "string" --> "String". If possible, you should set your editor to automatically reload files when using this option.',
185            'type': 'set',
186            'choices': ['none', 'bang-equals', 'case', 'all'],
187            'args': 'none|case|all',
188            'default': 'none',
189        },
190        {
191            'name': 'debug',
192            'synonyms': ['d'],
193            'type': 'string',
194            'description': 'Turn on system debugging information. The value 1 implies full, which enables attaching a debugger to a running program. Turning on implies -debugging-tips:no.',
195            'args': '0|1|pdbonly|full'
196        },
197        {
198            'name': 'debugging-tips',
199            'description': 'Display debugging tips when an unhandled exception occurs. Overridden by -exception-report.',
200            'type': 'bool',
201            'default': 'yes',
202        },
203        {
204            'name': 'delay-sign',
205            'type': 'bool',
206            'description': 'Delay-sign the assembly using only the public portion of the strong name key.',
207            'platforms': ['.net'],
208        },
209        {
210            'name': 'detailed-stack-trace',
211            'synonyms': ['dst'],
212            'type': 'bool',
213            'description': 'Enable a detailed stack trace which gives great postmortem information for uncaught exceptions, but slows execution. Also, detects stack overflow which is needed for Mono but not .NET which already does this. Works in combination with -exception-report, or see ObjectExplorer-WinForms.cobra and its doc string for ideas on how to present this information graphically.',
214        },
215        {
216            'name': 'document',
217            'synonyms': ['doc'],
218            'type': 'main',
219            'description': 'Generate HTML docs for the Cobra source.',
220        },
221        {
222            'name': 'document-library',
223            'synonyms': ['doc-lib'],
224            'is-main': true,
225            'type': 'string',
226            'description': 'Generate HTML docs for a library.',
227        },
228        {
229            'name': 'embed-run-time',
230            'synonyms': ['ert'],
231            'type': 'bool',
232            'default': 'no',
233            'description': 'Embed the Cobra run-time support code in the assembly so that no reference to an external Cobra.Lang.dll is required. Approximately 85KB - 130KB overhead depending on other options such as -include-asserts or -turbo.',
234        },
235        {
236            'name': 'exception-report',
237            'synonyms': ['exc-rpt', 'er'],
238            'type': 'bool',
239            'description': 'Turn on an informative HTML report that will be generated if the program throws an uncaught exception. Also, see ObjectExplorer-WinForms.cobra and its doc string for an alternative, GUI approach.',
240        },
241        {
242            'name': 'files',
243            'isAccumulator': true,
244            'description': 'Specify the files for Cobra to process in a separate text file. One file per line; # comments and blank lines are ignored.',
245            'args': 'filename',
246        },
247        {
248            'name': 'editor',
249            'type': 'string',
250            'description': 'Specify an editor and command line options to invoke if there is a compile-time error. Use underscore (_) for space in the specification. Can also set via COBRA_EDITOR environment variable (in which case spaces work fine).',
251            'example': ['uedit32_FILE/LINE', 'mate_FILE_-l_LINE'],
252            'args': 'editor_spec_with_FILE_LINE',
253        },
254        {
255            'name': 'extra-source',
256            'type': 'string',
257            'args': 'SOURCE',
258            'description': 'Add extra source code to be compiled with the rest. Generally used only internally by the compiler.',
259            'developer-only': true,
260        },
261        {
262            'name': 'help',
263            'synonyms': ['h'],
264            'type': 'main',
265            'description': 'Display this help message.',
266        },
267        {
268            'name': 'highlight',
269            'description': 'Write HTML versions of the source files with syntax highlighting.',
270            'type': 'main',
271        },
272        {
273            'name': 'include-asserts',
274            'type': 'bool',
275            'default': 'yes',
276            'description': 'Include assert statements in the program.',
277        },
278        {
279            'name': 'include-nil-checks',
280            'type': 'bool',
281            'default': 'yes',
282            'description': 'Include checks on non-nilable class variables, method arguments and "to !" casts.',
283        },
284        {
285            'name': 'include-tests',
286            'type': 'bool',
287            'default': 'yes',
288            'description': 'Includes unit tests for classes and members in the output assembly.',
289        },
290        {
291            'name': 'include-traces',
292            'type': 'bool',
293            'default': 'yes',
294            'description': 'Includes trace statements in the output assembly.',
295        },
296        {
297            'name': 'keep-intermediate-files',
298            'synonyms': ['kif'],
299            'type': 'bool',
300            'description': 'Keeps any intermediate files that Cobra generates, which are normally deleted. Example intermediate files are *.cobra.cs.',
301        },
302        {
303            'name': 'key-container',
304            'type': 'string',
305            'args': 'FILE',
306            'description': 'Specify a strong name key container used to strongname the output assembly.',
307            'platforms': ['.net'],
308        },
309        {
310            'name': 'key-file',
311            'type': 'string',
312            'args': 'FILE',
313            'description': 'Specify a strong name key file used to sign the output assembly.',
314            'platforms': ['.net'],
315        },
316        {
317            'name': 'legacy-one-default-initializer',
318            'type': 'bool',
319            'description': 'When a class does not provide initializers, Cobra will mirror each initializer in the base class. The old behavior was to provide one parameterless initializer. Use this option to switch to the old behavior.',
320        },
321        {
322            'name': 'library-directory',
323            'synonyms': ['lib'],
324            'type': 'string',
325            'isAccumulator': true,
326            'description': 'Specify additional directories to search in for references. Maps to -lib: on .NET and -classpath on JVM.',
327            'args': 'PATH',
328        },
329        {
330            'name': 'main',
331            'type': 'string',
332            'description': 'Specify the type containing the "main" method, particularly when more than one type declaration has a "main" method.',
333            'args': 'TYPENAME',
334        },
335        {
336            'name': 'namespace',
337            'synonyms': ['name-space', 'ns'],
338            'type': 'string',
339            'description': 'Set the namespace for all Cobra source files as if each one started with "namespace <name>". Can be a qualified name.',
340            'example': 'Foo.Bar',
341        },
342        {
343            'name': 'native-compiler',
344            'synonyms': ['sharp-compiler'],
345            'description': 'Specify the path to the back-end native compiler. This can be an executable (such as csc.exe or javac), a library (such as Cobra.Sharp.dll), "auto" or "provider".',
346            'type': 'string',
347            'args': 'file-system-path',
348            'default': 'auto',
349        },
350        {
351            'name': 'number',
352            'type': 'menu',
353            'choices': ['decimal', 'float', 'float32', 'float64'],
354            'args': 'decimal|float|float32|float64',
355            'default': 'decimal',
356            'description': "Set the real numeric type for both the 'number' type and fractional literals such as '1.0'.",
357        },
358        {
359            'name': 'optimize',
360            'synonyms': ['o'],
361            'type': 'bool',
362            'description': 'Enable optimizations.',
363        },
364        {
365            'name': 'out',
366            'type': 'string',
367            'description': 'Specify output file name (default: base name of first file). -test overrides this with a temporary program that runs only the unit tests and is then removed afterwards.',
368            'args': 'FILENAME',
369        },
370        {
371            'name': 'output-html',
372            'type': 'bool',
373            'description': "The command line's output will be in HTML.",
374        },
375        {
376            'name': 'pkg',
377            'type': 'string',
378            'isAccumulator': true,
379            'description': 'References package via "pkg-config --libs". (Mono only.)',
380            'args': 'NAME',
381            'restriction': 'mono',
382            'platforms': ['.net'],
383        },
384        {
385            'name': 'reference',
386            'synonyms': ['ref'], 
387            'isAccumulator': true,
388            'description': 'Add a DLL reference.',
389            'args': 'Some.dll',
390        },
391        {
392            'name': 'reveal-internal-exceptions',
393            'description': 'When true, uncaught exceptions from the Cobra compiler itself are not caught and wrapped in error messages. This is useful when developing on the Cobra compiler itself. Set to true if the environment variable COBRA_IS_DEV_MACHINE.',
394            'type': 'bool',
395            'developer-only': true,
396        },
397        {
398            'name': 'run',      # this is a switch only   
399            'synonyms': ['r'], 
400            'description': 'Runs the Cobra program. This is the default behavior if specify any Cobra source files.',
401            'type': 'main',
402        },
403        {
404            'name': 'run-args',
405            'synonyms': ['-'],  # '--'
406            'description': 'Remaining args are to be passed to executable.',
407            'eg':          '-- arg1 arg2 arg3',
408            'type': 'args-list',
409        },
410        {
411            'name': 'native-compiler-args',
412            'synonyms': ['sharp-args'],
413            'description': 'Pass additional arguments to the native back-end compiler (such as C# or Java).',
414            'isAccumulator': true,
415            'type': 'string',
416            'args': '"arg1 arg2"',
417        },
418        {
419            'name': 'target',
420            'synonyms': ['t'],
421            'description': 'Build a specific target.',
422            'type': 'menu',
423            'choices': ['exe', 'winexe', 'lib', 'module'],
424            'args': 'exe|winexe|lib|module',
425            'platforms': ['.net'],
426        },
427        {
428            'name': 'test',
429            'description': 'Run the unit tests in the code without running .main. Works for libraries too.',
430            'type': 'main',
431        },
432        {
433            'name': 'test-runner',
434            'type': 'string',
435            'description': 'Specify the method to invoke to run the unit tests. The method must be "shared". Typically the method will make use of classes in Cobra.Lang.Test to set up and initiate the test run.',
436            'default': 'Cobra.Lang.CobraCore.runAllTests',
437            'args': 'QUALIFIED-METHOD-NAME|nil',
438        },
439        {
440            'name': 'testify',
441            'description': '...',
442            'type': 'main',
443            'developer-only': true,
444        },
445        {
446            'name': 'testify-results',
447            'description': 'The filename to write the testify results to. Progress is still written to console.',
448            'type': 'string',
449            'default': 'r-testify',
450            'developer-only': true,
451        },
452        {
453            'name': 'testify-threads',
454            'description': '...',
455            'type': 'int',
456            'developer-only': true,
457        },
458        {
459            'name': 'timeit',
460            'description': 'Gives the total duration of running cobra (including the target program, if it is to be run). This is "wall time", not "cpu time".',
461            # although this option is implied by 'testify', the description does not say so, since 'testify' is a hidden option
462            'type': 'bool',
463        },
464        {
465            'name': 'turbo',
466            'description': 'Maximum run-time performance. This is a convenience for -contracts:none -include-asserts:no -include-nil-checks:no -include-tests:no -include-traces:no -optimize',
467            'type': 'bool',
468        },
469        {
470            'name': 'verbosity',
471            'synonyms': ['verbose', 'v'],
472            'type': 'int',
473            'min': 0,
474            'max': 5,
475            'args': 'N',
476            'description': 'Enable extra output from Cobra. Mostly useful for debugging Cobra and reporting problems. Values 0 - 5. Warning: Level 5 causes megabytes of output.',
477        },
478        {
479            'name': 'verbosity-ref',
480            'type': 'int',
481            'min': 0,
482            'max': 5,
483            'args': 'N',
484            'description': 'Enable extra output from Cobra regarding the resolution of references to libraries. Mostly useful for debugging Cobra and reporting problems. Values 0 - 5.',
485        },
486        {
487            'name': 'version',
488            'description': 'Print just the version number.', # ([.versionString]).',
489            'type': 'main',
490        },
491    ]
492
493
494class CommandLine
495    """
496    The main options that control the command line's behavior are:
497        run
498        test
499        compile
500        testify
501        help
502
503    "testify" is private to the implementor of Cobra.
504
505    "run" is the default if none are specified and at least one path is provided.
506
507    If no arguments are passed at all, "help" becomes the default.
508
509    You need to put at least one dash in front of an option. Also, you can leave
510    out the ".cobra" extension if you like. For example:
511
512        cobra -compile foo bar
513
514    """
515
516    get versionString as String is shared
517        ensure result.count(c'.') >= 2 or result.startsWith('svn:')
518
519        # Can't just take CobraCore.versionDescription as is, because that will be the one from Snapshot,
520        # not the current Source directory. And Snapshot can be a final release such as '0.7.4' for a
521        # period of time where this Cobra source represents an svn-post-RELEASE.
522        # Keep three components to the version number: X.Y.Z
523
524        ver = 'svn:[CompileTimeInfo.subversionRevision] (post 0.8) / [CompileTimeInfo.date]'
525        # ver += ', informal release 2009-03-01'
526        return ver
527
528    get platformString as String is shared
529        return .clrVersion + ' on ' + .opSysString
530
531    get clrVersion as String is shared
532        """ Get runtime version, like '.NET CLR v4.0.30319' or 'Mono 2.6.7 CLR v2.0.50727'. """
533        if CobraCore.isRunningOnMono
534            ver = 'Mono'
535            try
536                mvs = CobraCore.monoVersionString
537                if mvs and mvs.length > 0
538                    ver += ' ' + mvs.split(@[c' '], 2)[0]
539            catch
540                pass  # if the above fails for any reason, that's okay
541        else
542            ver = '.NET'
543        ver += ' CLR ' + Assembly.getAssembly(Object).imageRuntimeVersion       
544        return ver
545
546    get opSysString as String is shared
547        name = ''
548        if File.exists('/System/Library/CoreServices/SystemVersion.plist')
549            # Mac
550            content = File.readAllText('/System/Library/CoreServices/SystemVersion.plist')
551            match = Regex.match(content, r'<string>(Mac[^<]+)')
552            if match.success
553                name += match.groups[1].toString
554            match = Regex.match(content, r'<string>(\d+\.[\d\.]+)')
555            if match.success
556                name += ' ' + match.groups[1].toString
557        else if File.exists('/etc/lsb-release')
558            # Ubuntu, ...
559            content = File.readAllText('/etc/lsb-release')
560            d = Dictionary<of String, String>()
561            for line in content.splitLines
562                line = line.trim
563                if line == '' or line.startsWith('#') or '=' not in line, continue
564                pair = line.split('=', 2)
565                d[pair[0].trim] = pair[1].trim
566            if d.containsKey('DISTRIB_DESCRIPTION'), name = d['DISTRIB_DESCRIPTION'].trim
567            if name == ''
568                try
569                    name = d['DISTRIB_ID'] + ' ' + d['DISTRIB_RELEASE']
570                catch
571                    pass
572            if name.startsWith('"') and name.endsWith('"'), name = name[1:-1]
573        else if File.exists('/etc/arch-release')
574            # Arch Linux
575            name = 'Arch Linux ' + _getCommandOutput('/bin/uname', '-r').trim
576            pacman = _getCommandOutput('pacman', '--version')
577            match = Regex.match(pacman, r'[Pp]acman v?(\d+\.[\d\.]+)')
578            if match.success
579                name += ' (pacman ' + match.groups[1].toString.trim + ')'
580        else if File.exists('/etc/system-release')
581            # CentOS, Fedora, ...
582            name = File.readAllText('/etc/system-release')
583        else if File.exists('/etc/redhat-release')
584            # RedHat, ...
585            name = File.readAllText('/etc/redhat-release')
586        name = name.trim
587        if name == ''
588            # everyone else
589            name = Environment.osVersion.toString
590        return name
591
592    def _getCommandOutput(command as String, args as String) as String is shared
593        p = System.Diagnostics.Process()
594        p.startInfo.fileName = command
595        p.startInfo.arguments = args
596        p.startInfo.useShellExecute = false
597        try
598            return CobraCore.runAndCaptureAllOutput(p).trim
599        catch
600            # specific to users of this method: return '' rather than error
601            return ''
602
603    var _startTime as DateTime
604    var _verbosity = 0
605
606    var _options = OptionValues()
607    var _pathList as List<of String>?
608    var _htmlWriter as HtmlWriter?
609
610    var _compiler as Compiler?
611    var _argParser as ArgParser
612
613    cue init
614        base.init
615        _startTime = DateTime.now
616        _argParser = ArgParser(.versionString, nil)
617
618    get compiler from var
619
620    get options from var
621
622    get argParser from var
623
624    get verboseLineSeparator as String
625        try
626            w = Console.bufferWidth
627        catch IOException
628            w = 0
629        if w < 20, w = 80
630        w -= 1
631        return String(c'-', w)
632
633    get verbosity as int
634        return _verbosity
635
636    def parseArgs(args as List<of String>)
637        .parseArgs(args, out _options, out _pathList)
638
639    def parseArgs(args as IList<of String>, options as out OptionValues?, paths as out List<of String>?)
640        try
641            _argParser.parseArgs(args, out options, out paths)
642        catch ape as ArgParseException
643            options = nil
644            paths = nil
645            .error(ape.message)
646        _verbosity = _argParser.verbosity
647        CobraMain.willTimeIt = _argParser.willTimeIt
648       
649    def run
650        """
651        Run the command line using the command line arguments.
652        """
653        .run(CobraCore.commandLineArgs[1:])
654
655    def run(args as List<of String>)
656        """
657        Run the command line using the given arguments.
658        The `args` should include only the arguments and not the executable/program name.
659        """
660        if args.count == 0
661            .doAbout
662            return
663        .parseArgs(args)
664
665        if _options.boolValue('output-html')
666            _htmlWriter = HtmlWriter(Console.out)
667            dest = _htmlWriter to TextWriter
668        else
669            dest = Console.out
670        if _htmlWriter
671            stylePath = Path.combine(Path.getDirectoryName(CobraCore.exePath), 'styles-output-html.css')
672            _htmlWriter.writeHtml('<html><head><link href="file://[stylePath]" rel=stylesheet type="text/css"></head><body>[_htmlWriter.newLine]')
673        print to dest
674            paths = _pathList to !
675            options = _options
676            if .verbosity > 0
677                print 'Cobra Command Line [.versionString]'
678                print 'Copyright (C) 2003-[DateTime.now.year] by Cobra Language LLC.'
679                print
680                print 'OS Version:  ', Environment.osVersion
681                print 'CLR Platform:', if(CobraCore.isRunningOnMono, 'Mono', '.NET')
682                print 'CLR Version: ', Environment.version
683                print 'Current Directory: [Environment.currentDirectory]'
684                print 'Current Exe: [CobraCore.exePath]'
685                print 'Option Dictionary:'
686                options.print
687                print 'Paths:'
688                for path in paths
689                    print '    [path]'
690            if options.boolValue('testify')
691                .doTestify(paths)
692            else if options.boolValue('run')
693                .doRun(paths)
694            else if options.boolValue('test')
695                .doTest(paths)
696            else if options.boolValue('compile')
697                .doCompile(paths)
698            else if options.boolValue('highlight')
699                .doHighlight(paths)
700            else if options.boolValue('document')
701                .doDocument(paths)
702            else if options.isSpecified('document-library')
703                .doDocumentLibrary
704            else if options.boolValue('help')
705                .doHelp
706            else if options.boolValue('version')
707                .doVersion
708            else if options.boolValue('about')
709                .doAbout
710            else if options.boolValue('build-standard-library')
711                .doBuildStandardLibrary
712            else if not paths.count
713                .doHelp
714            else
715                .doRun(paths)
716        if _htmlWriter
717            _htmlWriter.writeHtml('</body></html>[_htmlWriter.newLine]')
718
719    def doHighlight(paths as List<of String>) as Compiler
720        """
721        Syntax highlighting via HTML generation.
722        """
723        comp = .doCompile(paths, false, false, do(c as Compiler)=c.lastPhase inherits BindInterfacePhase)
724        Node.setCompiler(comp)
725        try
726            comp.highlightFiles
727        finally
728            Node.setCompiler(nil)
729        return comp
730
731    def doCompile(paths as List<of String>) as Compiler
732        return .doCompile(paths, true, false, nil)
733
734    def doCompile(paths as List<of String>, willPrintSuccessMsg as bool, 
735                    writeTestInvocation as bool, stopCompilation as Predicate<of Compiler>?) as Compiler
736        sep = Path.directorySeparatorChar.toString
737        oldPaths = for path in paths get path.replace('\\', sep).replace('/', sep)
738        paths.clear
739        for path in oldPaths
740            if File.exists(path)
741                paths.add(path)
742            else if Directory.exists(path)
743                .error('Cannot process directories in general ("[path]").')
744            else
745                .error('Cannot find file "[path]".')
746        if paths.count == 0 and not .options.buildStandardLibrary
747            .error('No files to process.')
748        _compiler = c = Compiler(.verbosity)
749        c.commandLineArgParser = _argParser
750        c.options = _options
751        c.willPrintSuccessMsg = willPrintSuccessMsg
752        c.htmlWriter = _htmlWriter
753        try
754            c.compileFilesNamed(paths, writeTestInvocation, stopCompilation)
755            if _verbosity <> c.verbosity, _verbosity = c.verbosity
756        catch StopCompilation
757            # Each phase of the compiler may throw an exception to stop compilation.
758            # Before doing so, it prints its errors.
759            assert c.errors.count>0
760            if _options.containsKey('editor')
761                spec = _options['editor'] to String?
762            else
763                spec = Environment.getEnvironmentVariable('COBRA_EDITOR')
764            if spec and spec <> ''
765                if spec.indexOf('FILE')==-1
766                    .error('Missing FILE from editor spec.')
767                if spec.indexOf('LINE')==-1
768                    .error('Missing LINE from editor spec.')
769                i = spec.indexOf('_')
770                if i == -1
771                    i = spec.indexOf(' ')
772                    if i == -1
773                        .error('Missing underscore or space from editor spec.')
774                exeName = spec.substring(0, i)
775                args = spec.substring(i+1)
776                for error in c.errors
777                    if error.isError and error.hasSourceSite
778                        if error.fileName.trim <> ''
779                            # trace error.fileName, error.lineNum
780                            args = args.replace('FILE', error.fileName)
781                            args = args.replace('LINE', error.lineNum.toString)
782                            p = System.Diagnostics.Process()
783                            p.startInfo.fileName = exeName
784                            p.startInfo.arguments = args
785                            p.startInfo.useShellExecute = false
786                            if _verbosity >= 3
787                                print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]'
788                            try
789                                p.start
790                                p.waitForExit  # TODO: is this really needed?
791                            catch exc as Exception
792                                print 'Cannot invoke editor:'
793                                print '    Command: [p.startInfo.fileName] [p.startInfo.arguments]'
794                                print '    Exception: [exc]'
795                            break
796        CobraMain.compiler = c
797        return c
798
799    def doDocument(paths as List<of String>) as Compiler
800        comp = .doCompile(paths, false, false, do(c as Compiler)=c.lastPhase inherits BindInterfacePhase)
801        GenerateHtmlDocVisitor(do(module)=module inherits CobraModule).gen(comp)
802        return comp
803
804    def doDocumentLibrary
805        docModules = Set<of Module>()
806        comp = Compiler(.verbosity)
807        comp.commandLineArgParser = _argParser
808        comp.options = .options
809        Node.setCompiler(comp)
810        try
811            comp.initBackEnd
812            reference = .options['document-library'] to String  # to-do: pick out type name. to-do: support multiple references
813            reference = comp.backEnd.fixLibExtension(reference)
814            .options['reference'] = [reference]
815            phaseClasses = [
816                BindRunTimeLibraryPhase,
817                ReadLibrariesPhase,
818                BindUsePhase,
819                BindInheritancePhase,
820                BindInterfacePhase,
821                ComputeMatchingBaseMembersPhase,
822            ]
823            phases = for phaseClass in phaseClasses get phaseClass(comp) to Phase
824            hasErrors = false
825            for phase in phases
826                if not hasErrors or phase.willRunWithErrors
827                    if comp.runPhase(phase), hasErrors = true
828            # running a phase unsets the compiler, so set it again:
829            Node.setCompiler(comp)
830            comp.printMessages
831            if hasErrors, Environment.exit(1)       
832            for module in comp.modules.clone
833                if module.fileName.endsWith(reference)
834                    _prepIfNeeded(module)
835                    docModules.add(module)
836            GenerateHtmlDocVisitor(do(m)=docModules.contains(m to Module)).gen(comp)  # CC: shouldn't need typecast
837        finally
838            Node.setCompiler(nil)
839
840    def _prepIfNeeded(node)
841        if node inherits AssemblyModule
842            _prepIfNeeded(node.topNameSpace to passthrough)
843        else if node inherits NameSpace
844            for decl in node.declsInOrder
845                _prepIfNeeded(decl to passthrough)
846        else if node inherits Box
847            node.prepIfNeeded
848        else if node implements INameSpaceMember
849            # ex: Enum
850            pass
851        else
852            throw FallThroughException(node)
853
854    def doTest(paths as List<of String>)
855        if paths.count == 0
856            .error('You must specify one or more Cobra files to run unit tests for.')
857        c = .doCompile(paths, false, true, nil)
858        if c.errors.count
859            print 'Not running tests due to errors above.'
860            return
861        testInvoker = c.modules.last
862        assert testInvoker inherits NativeModule
863        assert testInvoker.fileName.startsWith('test-')
864        File.delete(testInvoker.fileName)
865        try
866            p = c.runProcess
867            if _verbosity >= 1
868                print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]'
869                print .verboseLineSeparator
870            p.startInfo.useShellExecute = false
871            p.start
872            p.waitForExit  # TODO: is this necessary?
873        catch
874            try, File.delete(c.fullExeFileName)
875            catch IOException, pass
876            catch UnauthorizedAccessException, pass
877            throw
878        success
879            try
880                File.delete(c.fullExeFileName)
881            catch IOException
882                print 'warning: Cannot remove "[c.fullExeFileName]"'
883            catch UnauthorizedAccessException
884                print 'warning: Cannot remove "[c.fullExeFileName]"'
885
886    def doRun(paths as List<of String>)
887        c = .doCompile(paths, false, false, nil)
888        if c.errors.count
889            print 'Not running due to errors above.'
890            return
891        if .options.boolValue('compile')  # maybe changed by compiler directive
892            return
893
894        sw = System.Diagnostics.Stopwatch()
895        sw.start
896   
897        exeFileName as String? = nil
898        runArgs = .options.getStringList('run-args')
899        # TODO: what's this?
900        # exeArgs = .options.getDefaultLOStr('exe-args')
901        # if exeArgs.count
902        #   exeFileName = exeArgs[0]
903        #   runArgs = exeArgs[1:]
904        p = c.runProcess(exeFileName, runArgs)
905        if _verbosity >= 1
906            print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]'
907            print .verboseLineSeparator
908        p.startInfo.useShellExecute = false
909        try
910            p.start
911            p.waitForExit  # TODO: is this necessary?
912        catch exc as Exception
913            print 'ERROR: Compilation succeeded, but cannot run "[p.startInfo.fileName]" because: [exc.typeOf.name]: [exc.message]'
914            print 'You may be able to launch the executable program directly from the command line.'
915            if 'elevation' in exc.message
916                print 'If you are on Windows Vista, using an admin or power user account may avoid this problem.'
917            Environment.exit(1)
918        sw.stop
919        CobraMain.runTime = sw.elapsed
920       
921    def doHelp
922        .doAbout
923        print ''
924        print 'Usage:'
925        print ''
926        print '  cobra <options> <filename>'
927        print '    * run filename'
928        print '    * compile if needed'
929        print '    * .cobra extension is optional'
930        print ''
931        print '  cobra <options> <command> <path(s)>'
932        print '    * commands that operate on path(s) are:'
933        print '      -compile .... Compile only. Also, -c'
934        print '      -run ........ Run the program (compile if necessary). Also -r (Default)'
935        print '      -test ....... Run the unit tests of a library.'
936        print '      -document ... Document the program (partial compilation). Also, -doc'
937        print '      -highlight .. Syntax highlight the program in HTML.'
938        print ''
939        print '  cobra <options> <command>'
940        print '    * standalone commands are:'
941        print '      -help ...... Print full help information.'
942        print '      -about ..... Print name, copyright, etc. no usage.'
943        print '      -version ... Print just the version number ([.versionString]).'
944        print ''
945        print '  <options> can be:'
946
947        # print options from their specs (but not the main ones which are covered above)
948        leftMarginStr = '        '
949        width = _calcWidth()
950        for spec in CommandLineOptionSpecs().specsList
951            if spec.isMain, continue
952            if spec.containsKey('developer-only') and spec['developer-only'] to bool and not Utils.isDevMachine
953                continue
954            if spec.isOptionSpecRestrictionViolated
955                continue
956            print
957            print '    -[spec["name"]]' stop
958            if spec.containsKey('args')
959                if spec.hasDefault
960                    lbracket = r'['
961                    print '[lbracket]:[spec["args"]]]' stop
962                else
963                    print ':[spec["args"]]' stop
964            else if spec.type == 'bool'
965                print r'[:no|yes]' stop
966            if spec.hasDefault
967                print '  default is [spec.default]' stop
968            print
969            if spec.synonyms.count
970                print '    ' stop
971                sep = ''
972                for syn in spec.synonyms
973                    print '[sep]-[syn]' stop
974                    sep = ', '
975                print
976            s = spec.description
977            while s.length
978                if s.length < width
979                    print '[leftMarginStr][s]'
980                    s = ''
981                else
982                    # TODO: bug in here for narrow widths. try "width = 20" to reproduce
983                    j = width + 1
984                    if j >= s.length, j = s.length - 1
985                    while j > 0 and s[j] <> ' ', j -= 1
986                    if j
987                        sub = s.substring(0, j)
988                        s = if(s.length, s.substring(j+1), '')
989                        print '[leftMarginStr][sub]'
990            if spec.containsKey('example')
991                if spec['example'] inherits System.Collections.IList
992                    first = true
993                    for example in spec['example'] to System.Collections.IList
994                        if first, print '[leftMarginStr]Examples: ' stop
995                        else,     print '[leftMarginStr]          ' stop
996                        print '-[spec["name"]]:[example]'
997                        first = false
998                else
999                    print '[leftMarginStr]Example: -[spec["name"]]:[spec["example"]]'
1000            if spec.containsKey('eg') # verbatim example line
1001                print '[leftMarginStr]e.g. [spec["eg"]]'
1002
1003    def doAbout
1004        print
1005        print 'The Cobra Programming Language [.versionString]'
1006        print 'on [.platformString]'
1007        print 'at [Assembly.getEntryAssembly.location]'
1008        print
1009        print 'Copyright (C) 2003-[DateTime.now.year] by Cobra Language LLC.  All Rights Reserved.'
1010        print ''
1011        print 'On the web:  http://cobra-language.com/'
1012        print 'Source:      http://cobra-language.com/source'
1013        print 'Support:     http://cobra-language.com/support'
1014        print 'License:     http://www.opensource.org/licenses/mit-license.php'
1015        print
1016        print 'Usage:       cobra -h'
1017
1018    def _calcWidth as int
1019        leftMargin = 8
1020        try
1021            consoleWidth = Console.windowWidth
1022        catch IOException
1023            # 2008-04-11, When redirecting output, MS .NET 2.0 throws IOException while Novell Mono 1.9 returns 0
1024            consoleWidth = 0
1025        if consoleWidth < 1
1026            try
1027                consoleWidth = Console.bufferWidth
1028            catch IOException
1029                consoleWidth = 0
1030        totalWidth = consoleWidth - 2
1031        if totalWidth < 0,  totalWidth = 0
1032        if totalWidth == 0, totalWidth = 78
1033        else if totalWidth < 20, totalWidth = 20
1034        assert totalWidth > 0
1035        width = totalWidth - leftMargin
1036        assert width > 0
1037        return width
1038       
1039    def doVersion
1040        print 'Cobra [.versionString] on [.platformString]'
1041
1042    def error(msg as String)
1043        if msg.length
1044            print 'cobra: error: [msg]'
1045            print 'Run Cobra without options to get full usage information.'
1046        Environment.exit(1)
1047
1048
1049    ## Build Standard Library
1050
1051    def doBuildStandardLibrary
1052        v = .verbosity
1053        if v, print 'Building standard library'
1054        dllInfo = FileInfo('Cobra.Lang.dll')
1055        if dllInfo.exists
1056            prevName = 'Cobra.Lang-previous.dll'
1057            if v, print 'Renaming Cobra.Lang.dll to [prevName]'
1058            prevInfo = FileInfo(prevName)
1059            try
1060                if prevInfo.exists, prevInfo.delete
1061            catch UnauthorizedAccessException
1062                print 'warning: Cannot delete [prevName]'
1063            success
1064                try
1065                    FileInfo('Cobra.Lang.dll').moveTo(prevName)
1066                catch UnauthorizedAccessException
1067                    print 'warning: Cannot move Cobra.Lang.dll to [prevName]'
1068        _options['target'] = 'lib'
1069        _options['include-tests'] = false
1070        _options['embed-run-time'] = true  # because the runtime is what we're building!
1071
1072        # embed the version
1073        reMatch = Regex(r'^\d+\.\d+\.\d+\b').match(.versionString)
1074        if reMatch.success
1075            version = reMatch.value to !
1076        else
1077            reMatch = Regex(r'^svn:(\d+)\b').match(.versionString)
1078            if reMatch.success
1079                version = reMatch.groups[1].value to !
1080            else
1081                print 'warning: Cannot extract version number from version string: [.versionString]'
1082                version = '999'
1083            version = '0.0.' + version
1084        assert version.count(c'.') == 2  # ex: '0.8.0'
1085        if ' ' in .versionString or 'post' in .versionString  # ex: '0.8.0 post'
1086            # 'post' versions have a fourth version component of 1, as opposed to 0
1087            version += '.1'
1088            assert version.count(c'.') == 3
1089        _options.addExtraUse('use System.Reflection')
1090        _options.addExtraSource("assembly\n\thas AssemblyVersion('[version]')\n")
1091        .doCompile(List<of String>(), true, false, nil)
1092
1093
1094    ## Testify
1095
1096    def doTestify(paths as List<of String>)
1097        """
1098        Used internally for testing cobra during development.
1099        Why not just 'test'? because that is reserved for regular developers to run true unit tests.
1100        """
1101        TestifyRunner(_startTime, this, paths).run
1102       
1103   
1104
1105class ArgParseException
1106    inherits Exception
1107
1108    cue init(msg as String?)
1109        base.init(msg)
1110
1111
1112class ArgParser
1113    """
1114    Parse command line arguments into a dictionary of recognized OptionValues and a list of paths.
1115    """
1116
1117    var _versionString as String
1118    var _verbosity = 0
1119    var _willTimeIt = false
1120    var _optsOnly = false
1121   
1122    get versionString from var
1123    get verbosity from var
1124    get willTimeIt from var
1125
1126    var _optionSpecs as List<of OptionSpec>
1127
1128    var _specDict as Dictionary<of String, OptionSpec>
1129        # will contain keys for all spec names and their synonyms
1130
1131    var _synToName as Dictionary<of String, String>
1132        # maps synonyms to their full names
1133
1134    var _synList as List<of String>
1135
1136    cue init(version as String, optionSpecs as List<of OptionSpec>?)
1137        base.init
1138        _versionString = version
1139        # prep the option specs
1140        if optionSpecs
1141            _optionSpecs = List<of OptionSpec>(optionSpecs)
1142        else
1143            _optionSpecs = CommandLineOptionSpecs().specsList
1144        _specDict = Dictionary<of String, OptionSpec>()
1145        _synToName = Dictionary<of String, String>()
1146        _synList = List<of String>()
1147        _initSynonyms
1148
1149    def parseToOptions(args as IList<of String>) as OptionValues
1150        """
1151        Reuse ArgParser to parse some additional string option args. Files are not allowed.
1152        Return new set of Options from given args list
1153        """
1154        opts = OptionValues()
1155        paths = List<of String>()
1156        # TODO: mark which opts as unusable in this context and filter out
1157        _optsOnly = true
1158        .parseArgs(args, out opts, out paths)
1159        if .verbosity or opts.getDefault('verbosity', 0) to int
1160            print 'parseToOptions Option Dictionary:'
1161            opts.print
1162        return opts
1163       
1164    def parseArgs(args as IList<of String>, options as out OptionValues?, paths as out List<of String>?)
1165        """
1166        Parse command line arguments: options and files.
1167        The `args` should include only the arguments and not the executable/program name.
1168        """
1169        _optsOnly = false
1170        _parseArgs(args, out options, out paths)
1171       
1172    def _parseArgs(args as IList<of String>, options as out OptionValues?, paths as out List<of String>?)
1173        ensure
1174            options
1175            paths
1176        body
1177            optionPrefix = '-'
1178            if not args.count
1179                options = OptionValues()
1180                options.add('about', true)
1181                paths = List<of String>()
1182                return
1183
1184            # set up initial valueDict
1185            valueDict = Dictionary<of String, Object>()
1186            didSpecify = Dictionary<of String, bool>()  # CC: could just be a Set
1187            if Utils.isDevMachine
1188                valueDict['reveal-internal-exceptions'] = true  # this is a specially computed default, but can still be overridden on the command line
1189
1190            valueStr = 'no-value'
1191            fileList = List<of String>()
1192            mainOptions = List<of String>()
1193            argn = 0
1194            for arg in args
1195                argn += 1  # offset next arg after current
1196                if not arg.trim.length, continue
1197
1198                isOption = arg.startsWith(optionPrefix)
1199                if isOption
1200                    name = _getOptionParts(arg, optionPrefix, out valueStr)
1201                    assert name.trim <> ''
1202                    spec = _specDict[name]
1203
1204                    if spec.isAccumulator
1205                        _accumulateOptValue(name, valueStr, spec, valueDict, didSpecify)
1206                        continue
1207                       
1208                    value = _processToValue(name, valueStr, spec, mainOptions)
1209                    if value == 'args-list'
1210                        value = args[argn:]
1211                    if value is nil
1212                        errMsg = 'Cannot parse value "[valueStr]" for option "[name]".'
1213                        branch spec.type
1214                            on 'bool', errMsg += ' Possible values include yes, no, y, n, true, false, t, f, 1, 0, + and -.'
1215                            on 'menu', errMsg += ' Possible values are [spec.choices.join(", ", " and ")].'
1216                        _error(errMsg)
1217                    valueDict[name] = value to !
1218                    didSpecify[name] = true
1219                    if spec.type == 'args-list'
1220                        break   # absorbed remainder of args
1221                else # not isOption
1222                    if _optsOnly
1223                        _error('Filenames are not allowed here, All the args provided must be "-" prefixed options')
1224                    if arg.startsWith('/')
1225                        errHint = ' If you meant to specify an option, use dash (-) instead of slash (/).'
1226                    _processAsFile(arg, fileList, errHint)
1227
1228            _handleSynonyms(valueDict)         
1229            _addInDefaults(valueDict)       
1230
1231            # TODO: make the option names case-insensitive
1232
1233            if mainOptions.count > 1
1234                _error('Cannot have these main options at the same time: [mainOptions.join(", ")]')
1235
1236            _unpackOptions(valueDict, fileList)
1237
1238            # set the out parameters
1239            options = OptionValues(valueDict)
1240            options.setSpecified(didSpecify)
1241            paths = fileList
1242            _computeArgImplications(options to !)
1243       
1244    def _getOptionParts(arg as String, optionPrefix as String, valueStr as out String) as String
1245        arg = .fixOptionArg(arg, optionPrefix) 
1246        # CC: name, valueStr = .splitOpt(arg)
1247        parts = .splitOpt(arg) 
1248        name = parts[0]
1249        valueStr = parts[1]
1250        name = .validateOptionName(name)
1251        return name         
1252
1253    def fixOptionArg(arg as String, optionPrefix as String) as String   
1254        """
1255        Strip any leading switch chars.
1256        """
1257        while arg.startsWith(optionPrefix)
1258            arg = arg[1:]
1259        if not arg.length  # '--'
1260            arg = 'run-args'
1261        return arg
1262
1263    def splitOpt(arg as String) as IList<of String>
1264        """
1265        Split option into name and valueStr
1266        """
1267        valuePrefix = c':'
1268        parts = arg.split(@[valuePrefix], 2)
1269        if parts.length == 1
1270            name = parts[0]
1271            if name.endsWith('+')
1272                name = name[:-1]
1273                valueStr = 'on'
1274            else if name.endsWith('-')
1275                name = name[:-1]
1276                valueStr = 'off'
1277            else
1278                valueStr = 'on'  # this should probably be done at a later point, like _processToValue
1279        else
1280            assert parts.length == 2
1281            name = parts[0]
1282            valueStr = parts[1]
1283        assert name.length, [arg, parts]
1284        # assert valueStr.length  # not valid. an option could be cleared out like: -editor:""
1285        return [name, valueStr] 
1286       
1287    def _initSynonyms
1288        """
1289        Init supporting data structures for handling option synonyms       
1290        """     
1291        for spec in _optionSpecs
1292            if spec.isOptionSpecRestrictionViolated
1293                continue
1294            _specDict[spec.name] = spec
1295            if spec.synonyms.count
1296                for syn in spec.synonyms
1297                    assert not _specDict.containsKey(syn)
1298                    _specDict[syn] = spec
1299                    _synToName[syn] = spec.name
1300                    _synList.add(syn)
1301           
1302    def validateOptionName(name as String) as String
1303        """
1304        Ensure the given name exists as an option name or synonym mappable
1305        to an option name; return the canonical name for the option/synonym
1306        """
1307        require name.trim <> ''
1308        ensure result.trim <> ''
1309        name = _synToName.get(name, name)
1310        assert name.trim <> ''
1311        if not _specDict.containsKey(name)
1312            msg = 'No such option "[name]".'
1313            if name.contains('=')
1314                msg += ' If you meant to specify an option value, use colon (:) instead of equals (=).'
1315            _error(msg)
1316        return name
1317       
1318    def _accumulateOptValue(name as String, valueStr as String,
1319            spec as OptionSpec,
1320            valueDict as Dictionary<of String, Object>,
1321            didSpecify as Dictionary<of String, bool>)
1322        # accumulators are always treated as strings. TODO: assert that
1323        value = _interpretValue(valueStr, spec)
1324        if valueDict.containsKey(name)
1325            (valueDict[name] to System.Collections.IList).add(value)
1326        else
1327            valueDict[name] = [value to String]
1328            didSpecify[name] = true
1329
1330    def _fixDebug(valueStr as String) as String?
1331        if valueStr == 'pdbonly' or valueStr == 'full'
1332            return valueStr
1333           
1334        value as String? = 'no-value'   
1335        try
1336            b = _boolForString(valueStr)
1337        catch FormatException
1338            value = nil
1339        success
1340            value = if(b, '+', '-')
1341        return value
1342   
1343    def _processToValue(name as String, valueStr as String, spec as OptionSpec, mainOptions as List<of String>) as dynamic?
1344        value = 'no-value' to dynamic?
1345   
1346        if name == 'debug'      # special case
1347            return _fixDebug(valueStr)
1348       
1349        branch spec.type
1350            on 'main'
1351                mainOptions.add(name)
1352                value = true
1353            on 'args-list'  # remainder of args are for execution of exe file
1354                value = 'args-list'
1355            else
1356                value = _interpretValue(valueStr, spec)
1357        return value
1358
1359    def _handleSynonyms(valueDict as Dictionary<of String, Object>)
1360        for syn in _synList
1361            if valueDict.containsKey(syn)
1362                valueDict[_synToName[syn]] = valueDict[syn]
1363                valueDict.remove(syn)
1364           
1365    def _addInDefaults(valueDict as Dictionary<of String, Object>)
1366        for spec in _optionSpecs
1367            if not valueDict.containsKey(spec.name) and spec.hasDefault
1368                defaultValue = _interpretValue(spec.default, spec) to !
1369                if .verbosity
1370                    print 'Setting option "[spec.name]" to default value [defaultValue].'
1371                valueDict[spec.name] = defaultValue
1372
1373    def _unpackOptions(valueDict as Dictionary<of String, Object>, fileList as List<of String>) 
1374        """
1375        Unpack certain options (verbosity and timeit) into specific class fields,
1376            do files option processing
1377        """
1378        if valueDict.containsKey('verbosity')
1379            _verbosity = valueDict['verbosity'] to int
1380
1381        if not valueDict.containsKey('timeit') and valueDict.containsKey('testify')
1382            valueDict['timeit'] = true
1383        if valueDict.containsKey('timeit')
1384            _willTimeIt = valueDict['timeit'] to bool
1385
1386        if valueDict.containsKey('files')
1387            fileNamesList = valueDict['files'] to System.Collections.IList
1388            _processFilesFile(fileNamesList, fileList)
1389
1390    def readFilesFile(filesFilePath as String) as List<of String>
1391        """
1392        Augments the list of files held by the arg parser with those passed in.
1393        """
1394        paths = List<of String>()
1395        _processFilesFile([filesFilePath], paths)
1396        return paths
1397
1398    def _processFilesFile(fileNamesList as System.Collections.IList, fileList as List<of String>)
1399        """
1400        Treat entries in fileNamesList as names of files containing filenames to compile,
1401        validate names and add into fileList
1402        """
1403        for fileName as String in fileNamesList
1404            try
1405                try
1406                    baseDir = Path.getDirectoryName(fileName)
1407                catch ArgumentException
1408                    _error('Cannot open file "[fileName]" which appears to be an invalid path.')
1409                for line in File.readAllLines(fileName)
1410                    line = line.trim
1411                    if line.length==0 or line.startsWith('#'), continue
1412                    # note that source files are relative to the location of the "files file"
1413                    try
1414                        fileArg = Path.combine(baseDir, line)
1415                    catch ArgumentException
1416                        _error('Cannot properly read file "[fileName]" for file names.')
1417                    _processAsFile(fileArg, fileList, nil)
1418            catch IOException
1419                _error('Cannot open file "[fileName]".')
1420
1421    def _processAsFile(arg as String, fileList as List<of String>, errHint as String?)
1422        """
1423        Validate arg as filename and on success add into fileList
1424        """
1425        sep = Path.directorySeparatorChar.toString
1426        arg = arg.replace('\\', sep).replace('/', sep)
1427        if File.exists(arg)
1428            fileList.add(arg)
1429        else if File.exists(arg+'.cobra')
1430            fileList.add(arg+'.cobra')
1431        else if Directory.exists(arg)
1432            fileList.add(arg)
1433        else
1434            _error('Cannot find "[arg]" as a file.' + (errHint?''))
1435
1436    def _computeArgImplications(options as OptionValues)
1437        if options.getDefault('target', '') == 'lib' and not options.isSpecified('compile')
1438            options['compile'] = true
1439        if options.getDefault('debug', '') not in ['', '0', '-'] and not options.isSpecified('debugging-tips')
1440            options['debugging-tips'] = false
1441        if options.boolValue('turbo')
1442            options['contracts'] = 'none'
1443            options['include-asserts'] = false
1444            options['include-nil-checks'] = false
1445            options['include-tests'] = false
1446            options['include-traces'] = false
1447            options['optimize'] = true
1448           
1449    def _interpretValue(valueStr as String, spec as OptionSpec) as dynamic?
1450        value as dynamic?
1451        branch spec.type
1452            on 'main'
1453                throw InvalidOperationException('This method does not handle the main type.')
1454            on 'bool'
1455                try
1456                    value = _boolForString(valueStr)
1457                catch FormatException
1458                    value = nil  # cannot process
1459            on 'int'
1460                if valueStr == 'on'  # set internally when there is no value
1461                    valueStr = '1'
1462                try
1463                    value = int.parse(valueStr)
1464                catch FormatException
1465                    value = nil
1466                catch OverflowException
1467                    value = nil
1468                # TODO: check min and max
1469            on 'string' or 'accumulator'
1470                if valueStr.startsWith('"') and valueStr.endsWith('"'), valueStr = valueStr[1:-1]
1471                else if valueStr.startsWith("'") and valueStr.endsWith("'"), valueStr = valueStr[1:-1]
1472                value = valueStr
1473            on 'menu'
1474                if valueStr.length == 0
1475                    value = 'all'
1476                else if not valueStr in spec.choices
1477                    value = nil
1478                else
1479                    value = valueStr
1480            on 'set'
1481                if valueStr in ['', 'on'], valueStr = 'all'
1482                valueSet = Set<of String>()
1483                for choice in valueStr.split(c',')
1484                    if choice == 'none'
1485                        valueSet = {'none'}
1486                    else if choice == 'all'
1487                        valueSet = Set<of String>(for choice in spec.choices where choice not in ['none', 'all'])
1488                    else
1489                        valueSet.add(choice)
1490                value = valueSet
1491            else
1492                throw FallThroughException(spec.type)
1493        return value
1494       
1495    def _boolForString(s as String) as bool
1496        if s.toLower in ['', '+', 'on', 'true', 't', 'yes', 'y', '1']
1497            return true
1498        else if s.toLower in ['-', 'off', 'false', 'f', 'no', 'n', '0']
1499            return false
1500        else
1501            throw FormatException()
1502       
1503    def _error(msg as String)
1504        throw ArgParseException(msg)
1505
1506
1507
1508class HtmlWriter
1509    inherits TextWriter
1510    """
1511    In support of the output-html option.
1512    """
1513
1514    var _otherWriter as TextWriter
1515    var _isWritingHtml as bool
1516
1517    cue init(otherWriter as TextWriter)
1518        base.init
1519        _otherWriter = otherWriter
1520
1521    get encoding as Encoding? is override
1522        return Encoding.default
1523
1524    def write(c as char) is override
1525        _otherWriter.write(c)
1526        if not _isWritingHtml and c == c'\n'
1527            _otherWriter.write('<br>')
1528
1529    def writeHtml(html as String)
1530        _isWritingHtml = true
1531        try
1532            .write(html)
1533        finally
1534            _isWritingHtml = false
1535
1536
1537class OptionValues inherits Dictionary<of String, Object>
1538
1539    var _isSpecified = Dictionary<of String, bool>()  # CC: could just be a Set
1540
1541    cue init
1542        base.init
1543        # .init(nil) - cannot call base.init(d) with nil
1544
1545    cue init(d as IDictionary<of String, Object>?)
1546        base.init(d)
1547
1548    def isSpecified(name as String) as bool
1549        """
1550        Returns true if the given option name is explicitly specified (as opposed to being present
1551        in the options dictionary due to having a default value).
1552        """
1553        return _isSpecified.containsKey(name)
1554
1555    def didSpecify(name as String)
1556        _isSpecified[name] = true
1557
1558    def setSpecified(specify as Dictionary<of String,bool>)
1559        for name in specify.keys
1560            .didSpecify(name)
1561
1562    def boolValue(key as String) as bool
1563        if .containsKey(key)
1564            return this[key] to bool
1565        else
1566            return false
1567
1568    def get(key as String) as dynamic?
1569        return this[key]
1570
1571    def getDefault(key as String, default as dynamic?) as dynamic?
1572        if .containsKey(key)
1573            return this[key]
1574        else
1575            return default
1576
1577    def getStringList(key as String) as List<of String>
1578        """
1579        Returns a List<of String> for the given key.
1580        If the key is not present, returns an empty list.
1581        """
1582        if .containsKey(key)
1583            return this[key] to List<of String>
1584        else
1585            return List<of String>()
1586
1587    # CC: def getDefault<of T>(key as String, value as T) as T ...
1588
1589    def setValue(key as String) as Set<of String>
1590        """ Returns an empty string if no such set exists. """
1591        if .containsKey(key)
1592            try
1593                return this[key] to Set<of String>
1594            catch InvalidCastException
1595                throw InvalidCastException('Cannot cast [CobraCore.toTechString(this[key])] for key "[key]" to Set<of String>.')
1596        else
1597            return Set<of String>()
1598
1599    def combine(options as OptionValues)
1600        if options is not this
1601            for key in options.keys
1602                this[key] = options[key]
1603
1604    def combineNew(options as OptionValues)
1605        if options is not this
1606            for key in options.keys
1607                if not .isSpecified(key)
1608                    this[key] = options[key]
1609
1610    def print
1611        for key in .keys
1612            print '    [key]: [CobraCore.toTechString(this[key])]'
1613
1614    get buildStandardLibrary as bool
1615        return .boolValue('build-standard-library')
1616
1617    def addExtraUse(useSource as String)
1618        es = .getDefault('extra-source', '') to String
1619        useSource = useSource.trim
1620        if not useSource in es
1621            es = useSource + '\n' + es
1622        this['extra-source'] = es
1623
1624    def addExtraSource(source as String)
1625        es = .getDefault('extra-source', '') to String
1626        es = es + source + '\n'
1627        this['extra-source'] = es
1628
1629
1630    var _uniqueIdentifier as String?
1631
1632    def uniqueIdentifier as String
1633        """
1634        Returns a unique id such as '95b141d670c19f2f20a820751897b9c6' which is guaranteed to be
1635        unique per Cobra process. Can be used in identifiers. Motivated by -embed-run-time
1636        option which suffixes the Cobra.Lang suffix to avoid collisions with other assemblies.
1637        """
1638        if _uniqueIdentifier is nil
1639            components = [DateTime.now, this, Environment.currentDirectory, Process.getCurrentProcess.id]
1640            _uniqueIdentifier = components.toPrintString.md5HashInHex
1641        return _uniqueIdentifier to !
1642
1643    def embedRunTimeSuffix as String
1644        if .boolValue('embed-run-time') and not .boolValue('build-standard-library')
1645            return '_ert_' + .uniqueIdentifier
1646        else
1647            return ''
Note: See TracBrowser for help on using the browser.