use System.Reflection use System.Diagnostics class StopCompilation inherits Exception """ Thrown by the compiler when it must stop compilation due to errors. This can happen at the end of the phases: * parsing * bind interface * bind implementation * code gen """ var _comp as Compiler cue init(comp as Compiler) base.init _comp = comp class UnexpectedInvocationException inherits SystemException """ Throw this when a class is not expecting a particular method to be invoked at runtime (even though it inherits it or must implement it for an interface). """ cue init(obj as Object) base.init('Not expecting invocation.') class LoadReferenceException inherits Exception """ Thrown when Loading a reference fails for some reason. FileNotFoundException, FileLoadException """ var fileName ='' var tag='' cue init(tag as String, fileName as String, msg as String) base.init(msg) .tag = tag .fileName = fileName def toString as String is override return '[.tag] [.fileName] [.message]' enum PlatformEnum Microsoft Novell class BackEnd is abstract """ Holder for items specific to the backEnd implementation """ var _tagToTypeName = Dictionary() """ Map from Typename tags used in compiler to backend specific qualified Type name""" cue init(compiler as Compiler) base.init _name = '' _runTimeLibFileName = 'rtl' _runTimeLibNativeSourceFileName = 'rtlSrc.ext' __compiler = compiler get name from var as String get cobraRuntimeLibFileName from _runTimeLibFileName as String """Name of the Cobra RunTime Library file for this backend.""" get runTimeLibNativeSourceFileName from _runTimeLibNativeSourceFileName as String """Name of the backEnd source file containing any native code support for Cobra.Lang.""" get compiler from __compiler as Compiler get tagToTypeName from var """ Map from TypeName tags used in compiler to backend specific qualified TypeName""" def resolveTypeTag(qualifiedNameOrTag as String) as String if qualifiedNameOrTag.contains('.') qualifiedName = qualifiedNameOrTag else qualifiedName = .tagToTypeName[qualifiedNameOrTag] # type tag to backend qualifiedName return qualifiedName def makePhases(phases as IList) is abstract """ Given a list of core phases for compilation complete it with additions (or even removals and rearrangements if necessary) for this given backend. """ def getRecommendedBuiltInType(parentNameSpace as NameSpace?, name as String) as String? is abstract """ If parentNameSpace is the main system namespace and name is a backEnd builtin type return the Cobra equivalent language type name (or nil). """ def computeOutName as String is abstract """ Return the binary file output name for compilation of files for this backend. """ def genNativeModule(filename as String, verbosity as int) as Module? is abstract """ Check if a filename is a Native module and if so generate and return the Native module type for it otherwise return nil. """ def setupRunProcess(baseExe as String, fullExe as String) as Process is abstract """ Create and initialise the process to run the compiled program post compilation setup varies on the backend (.Clr/Jvm) and platform. """ def setDefaultUseDirectives(ns as NameSpace) is abstract """ Set the default Use directives into the given Namespace (usually topNamespace) for this backend. """ def fixLibExtension(libRef as String) as String is abstract """ Augment given lib reference string with backend extension if not already have one. """ def loadLibReference(reference as String) as bool is abstract """ Load the given library reference file using the current backend paradigms. Return true if reference found and loaded correctly, false otherwise """ def readSystemTypes is abstract """ Read and Load System Types for backend for Compiler to make available""" def fixMemberSigs is abstract """ Most backends dont natively support nilablility, this marks/fixes the common backend class signatures that should be marked nilable. """ def installNativeMethods(box as Box, nativeType as NativeType) is abstract """ Setup so that static methods (on Primitives and some others) get installed/treated as normal methods on the instance. """ def isRunnableFile( fullExeFileName as String) as bool is abstract """Test if given filename is an executable ( vs a library or something else) """ # Native Types access def cobraNameForNativeBoxName(name as String) as String is abstract """ Returns name from backend library entries converted to cobra naming form. """ get objectTypeProxy as AbstractTypeProxy is abstract """BE TypeProxy for root of back end Object hierarchy.""" get typeTypeProxy as AbstractTypeProxy is abstract """BE TypeProxy for BE notion of a class describing a Type.""" def nativeTypeProxy(type as NativeType) as NativeTypeProxy is abstract """ Return a Proxy placeHolder for a BackEnd Native Type. """ def nativeType(type as dynamic) as NativeType is abstract """ Return a Native Type wrapped so we can use it without explicitly knowing what it is anywhere else but the providing back end. Used by the backends to generate Types from Libraries. """ def nativeTypeByName(qualifiedName as String) as NativeType is abstract """ Return a Native Type corresponding to the fully qualified name. Abstract Type Literal tags used in the compiler directly can also be obtained through this otherwise the qualified names are expected to conform to the platform back end naming. """ def prepSystemObjectClass(box as Box) is abstract """Setup additional or convenience members on the System Object class. """ def scanGenericArgs(box as Box) is abstract """Scan a loaded Dll (generic) type and translate from native any generic Args.""" def scanNativeType(box as Box) is abstract """Scan a loaded Dll type and convert its native Type info to Cobras form """ def scanNativeType(edcl as EnumDecl) is abstract """Scan a loaded Dll Enum type and convert its native info to Cobras form """ def setUnderlyingType(edcl as EnumDecl) is abstract """Set underlying Storage Type of the value of an Enum.""" def determineExtnNativeType(extn as Extension, nativeType as NativeType) as NativeType """ The real extended type is the type of the first argument of any method. Find and make that type from the param list. """ throw Exception('Need implementation of determineExtnNativeType for backend to determine type for an extension') def handleNameSpaceNameCollision(ns as NameSpace, token as IToken, name as String) as NameSpace """ What to do if a namespace name collides with an existing symbol. Some backends disallow this situation so its an error. Others keep namespace and symbol tables separate so its perfectly allowable. """ throw Exception('In [ns.name] there is a already non-namespace declaration named "[name]".') class BasicBackEnd inherits BackEnd """ Stub BackEnd for tests. """ cue init(compiler as Compiler) base.init(compiler) _name = 'c#-clr(basic)' _runTimeLibFileName = 'cobraRTL.libext' _runTimeLibNativeSourceFileName = 'cobraRTL.ext' _tagToTypeName = { 'Object': 'System.Object', 'Type' : 'System.Type', } def makePhases(phases as IList) is override pass def getRecommendedBuiltInType(parentNameSpace as NameSpace?, name as String) as String? is override return nil def computeOutName as String is override return 'BasicOut' def genNativeModule(filename as String, verbosity as int) as Module? is override return nil def setupRunProcess(baseExe as String, fullExe as String) as Process is override p = Process() p.startInfo.fileName = 'echo' p.startInfo.arguments = 'basic BackEnd process - [baseExe]' return p def setDefaultUseDirectives(ns as NameSpace) is override pass def fixLibExtension(libRef as String) as String is override if not libRef.endsWith('.libext') libRef += '.libext' return libRef def loadLibReference(reference as String) as bool is override return true def readSystemTypes is override pass def fixMemberSigs is override pass def installNativeMethods(box as Box, nativeType as NativeType) is override pass def isRunnableFile(fullExeFileName as String) as bool is override return true # Native Type access def cobraNameForNativeBoxName(name as String) as String is override return name + '_BBE' def prepSystemObjectClass(box as Box) is override pass def scanGenericArgs(box as Box) is override pass def scanNativeType(box as Box) is override pass def scanNativeType(edcl as EnumDecl) is override pass def setUnderlyingType(edcl as EnumDecl) is override pass # Types get objectTypeProxy as AbstractTypeProxy is override return ClrTypeProxy(Object) # for testing get typeTypeProxy as AbstractTypeProxy is override return ClrTypeProxy(Type )# for testing def nativeTypeProxy(type as NativeType) as NativeTypeProxy is override return ClrTypeProxy(type) #TODO: fix this to something non BE specific Tmp def nativeType(type) as NativeType is override return ClrNativeType(type) #TODO: fix this to something non BE specific Tmp def nativeTypeByName(qualifiedName as String) as NativeType is override return ClrNativeType(System.Object) #TODO: fix this to something non BE specific Tmp class Compiler implements ITypeProvider, IWarningRecorder, IErrorRecorder, ICompilerForNodes is partial """ General notes: To cope with nesting, the compiler keeps a stack of the three types of things that can be nested. Items are pushed and popped off these stacks during each of the "bind interface" and "bind implementation" phases. The three stacks are: * nameSpaceStack - namespaces * boxStack - boxes are classes, structs and interfaces * codeMemberStack - methods, properties and inits """ var _backEnd as BackEnd? var _nextSerialNum as int is shared var _serialNum as int var _verbosity as int var _options as OptionValues var _commandLineArgParser as ArgParser? var _willPrintSuccessMsg = true var _willWriteTestInvocation = false var _htmlWriter as HtmlWriter? var _globalNS as NameSpace var _modules as List var _curModule as Module? # set during bindInt and bindImp var _nodeStack as Stack var _nameSpaceStack as Stack var _boxStack as Stack var _boxMemberStack as Stack var _codeMemberStack as Stack var _errors as List var _warnings as List var _messages as List # all errors and warnings var _messagesPerSourceLine as IDictionary> # key is 'filename:lineNum' var _noWarningLines as Cobra.Lang.ISet # key is 'filename:lineNum' var _intermediateFileNames as List var _loadedReferences as List # caches var _primitiveToITypeCache as IDictionary? # Key is backend type for a Type ( e.g System.Type) cue init .init(0, nil) cue init(verbosity as int) .init(verbosity, nil) cue init(verbosity as int, initialModules as IList?) base.init Node.reset _nextSerialNum += 1 _serialNum = _nextSerialNum _verbosity = verbosity _modules = List() _options = OptionValues() _pathsToCompile = List() _globalNS = NameSpace(Token.empty, '(global)') _nodeStack = Stack() _nameSpaceStack = Stack() _boxStack = Stack() _boxMemberStack = Stack() _codeMemberStack = Stack() _messages = List() _messagesPerSourceLine = Dictionary>() _noWarningLines = Set() _errors = List() _warnings = List() _intermediateFileNames = List() _loadedReferences = List() if initialModules if false # TODO: not working yet for mod in initialModules if mod inherits AssemblyModule ns = mod.topNameSpace assert not ns.superNameSpace ns.unifyInto(_globalNS) # TODO: handle CobraModule too _modules.add(mod) def toString as String is override return '[.getType.name]([_serialNum])' get backEnd from var pro defaultOutName from var as String? pro verbosity from var get includeTests as bool return .options.boolValue('include-tests') get primitiveToITypeCache from var """ Returns a cache mapping BackEnd types to their corresponding ITypes. Populated and used by the backend TypeProxy implementations ( {Clr,Jvm,...}TypeProxy) """ set primitiveCache as IDictionary assert value.count <> 0 _primitiveToITypeCache = value get globalNS from var """ Holds all topLevel decls including refs to Namespaces used and referenced. """ pro mainMethodTypeName from var as String = '' get modules from var pro options from var get pathsToCompile from var as IList """ Phases are free to modify this list (usually by adding new paths to include in compilation). """ pro commandLineArgParser from var pro willPrintSuccessMsg from var pro willWriteTestInvocation from var get curPhase from var as Phase? pro curModule from var get lastPhase from var as Phase? get nodeStack from var get nameSpaceStack from var get curNameSpace as NameSpace require .nameSpaceStack.count return _nameSpaceStack.peek get boxStack from var get curBox as Box require .boxStack.count return _boxStack.peek get curBoxMember as IBoxMember require .boxMemberStack.count return _boxMemberStack.peek get boxMemberStack from var get curCodeMember as AbstractMethod require .codeMemberStack.count return _codeMemberStack.peek get codeMemberStack from var get errors from var get warnings from var get messages from var get noWarningLines from var get loadedReferences from var """ Returns the list of library references that were loaded. This could be more than what's specified on the command line due to UseDirective. And it could be less due to failures to load. """ pro htmlWriter from var """ Set this to support the output-html command line option. """ get hasDetailedStackTraceOption as bool return _options.boolValue('detailed-stack-trace') get willTrackLocals as bool # the box stack can be empty because of assembly; has SomeAttr if .boxStack.count # the code member stack can be empty due to class variables (ex: var _x = 1) return .hasDetailedStackTraceOption and (.codeMemberStack.count == 0 or .curCodeMember.parentBox.canHaveDetailedStackTrace) else return false pro refExprLevel from var as int get hasExceptionReportOption as bool return .options.boolValue('exception-report') pro linesCompiled from var as int pro nodesCompiled from var as int pro tokensCompiled from var as int def recordError(error as SourceException) """ Node calls this to record errors. """ if error inherits NodeMultiException for exc in error.exceptions _addMessage(exc) else _addMessage(error) def addIntermediateFile(path as String) _intermediateFileNames.add(path) ## Phases def phaseClasses as List """ Returns a list of phase classes in the order they should be run. Note that the back-ends also add their own phases. """ return [ BindRunTimeLibraryPhase, ReadLibrariesPhase, ParsePhase, BindUsePhase, BindInheritancePhase, BindInterfacePhase, ComputeMatchingBaseMembersPhase, BindImplementationPhase, IdentifyMainPhase, SuggestDefaultNumberPhase, ] def makePhases as List """ Returns a list of phase instances in the order they should be run. Consults the back-end. """ phases = for phaseClass in .phaseClasses get phaseClass(this) to Phase .backEnd.makePhases(phases) if .options.boolValue('timeit') phases.add(CountNodesPhase(this)) for i, phase in phases.numbered, phase.stableOrder = i phases.sort # see Phase.order and .compareTo return phases def runPhase(phaseType as Type) as bool require phaseType.isSubclassOf(Phase) ensure result implies .errors.count > old .errors.count return .runPhase(phaseType(this) to Phase) def runPhase(phase as Phase) as bool require phase.compiler is this not phase.isRunning ensure phase.didRun not phase.isRunning .lastPhase is phase result implies .errors.count > old .errors.count body oldErrorCount = .errors.count _curPhase = phase try phase.run .writeSourceCodeCorrections return .errors.count > oldErrorCount finally _curPhase = nil _lastPhase = phase ## Compiler def compileFilesNamed(paths as IList) .compileFilesNamed(paths, false, nil) def compileFilesNamed(paths as IList, writeTestInvocation as bool, stopCompilation as Predicate?) .initBackEnd .defaultOutName = if(.options.buildStandardLibrary, 'Cobra.Lang.dll', paths[0]) if .options.boolValue('compile-if-needed') outName = .backEnd.computeOutName if not .isCompilationNeeded(outName, paths) return if .options.boolValue('reveal-internal-exceptions') _compileFilesNamed(paths, writeTestInvocation, stopCompilation) .printMessages return try _compileFilesNamed(paths, writeTestInvocation, stopCompilation) catch StopCompilation throw catch exc as Exception # unexpected exception -- only StopCompilation should be thrown if exc inherits SourceException if exc.hasSourceSite .recordError(InternalError(exc.fileName, exc.lineNum, exc.message, exc)) else .recordError(InternalError(exc.message, exc)) else if exc inherits AssertException sn as SyntaxNode? if exc.this inherits SyntaxNode sn = exc.this to SyntaxNode else if exc.info inherits SyntaxNode sn = exc.info to SyntaxNode if sn .recordError(InternalError(sn.token.fileName, sn.token.lineNum, exc.message, exc)) else .recordError(InternalError(exc.message, exc)) else .recordError(InternalError(exc.message, exc)) .printMessages throw StopCompilation(this) success .printMessages def _compileFilesNamed(paths as IList, writeTestInvocation as bool, stopCompilation as Predicate?) timeit = .options.boolValue('timeit') and .verbosity timings, totalElapsed = [], 0.0f .willWriteTestInvocation = writeTestInvocation _pathsToCompile = paths # now, essentially, do this: # for phase in .makePhases, runPhase(phase) # but pay attention to things like errors and stopCompilation hasErrors = false for phase in .makePhases if stopCompilation and stopCompilation(this), break if not hasErrors or phase.willRunWithErrors sw = System.Diagnostics.Stopwatch() sw.start if .runPhase(phase), hasErrors = true sw.stop if timeit totalElapsed += sw.elapsed.totalSeconds timings.add([sw.elapsed, phase.description]) sw.reset if timeit # to see timings try: # cobra -c -v -timeit hello.cobra # timings.sort(do(a, b)=b[0].compareTo(a[0])) sharp'timings.Sort(delegate(object a, object b) { return ((System.TimeSpan)((System.Collections.Generic.List)b)[0]).CompareTo((System.TimeSpan)((System.Collections.Generic.List)a)[0]); })' print print 'Phase timings:' for timing in timings elapsed, description = timing[0].totalSeconds to float, timing[1] to String percent = 100 * elapsed / totalElapsed print ' [percent:00.00]% [elapsed:00.00]secs [description]' print ' 100.00% [totalElapsed:00.00]secs Total for all phases' print if hasErrors, _exitFromErrors def testifyFilesNamed(fileNames as IList, options as OptionValues, resultsWriter as IndentedWriter, verbose as bool) """ Compiles the given fileNames in support of "testify". Sets .options to the options arg. Will raise StopCompilation when an error occurs. """ saveError = Console.error Console.setError(CobraCore.printDestination) try .options = options bar = '----------------------------------------------------------------------------------------------------' .initBackEnd .defaultOutName = if(.options.buildStandardLibrary, 'Cobra.Lang.dll', fileNames[0]) _pathsToCompile = fileNames hasErrors = false for phase in .makePhases if verbose, print 'Testify phase:', phase.description if not hasErrors or phase.willRunWithErrors if .runPhase(phase), hasErrors = true if verbose .dumpModulesForTestify(resultsWriter, 'Modules after: [phase.description]') print bar .printMessages print bar if hasErrors, throw StopCompilation(this) # testify runner wants this # TODO: to display the intermediate source: # for module in .modules # if not module.isCobraLibrary # Utils.printSource(module.sharpSource) # print bar finally Console.setError(saveError) def isCompilationNeeded(outName as String, paths as IList) as bool """ Returns true if compilation is needed because the output file does not exist or a source file is newer than it. """ v = .verbosity if v, print 'Checking timestamps for -compile-if-needed' if not File.exists(outName) if v, print ' "[outName]" does not exist.' return true targetTime = File.getLastWriteTime(outName) if v >= 2, print ' "[outName]" last write time: [targetTime].' for path in paths srcTime = File.getLastWriteTime(path) if v >= 2, print ' "[path]" last write time: [srcTime].' if srcTime.compareTo(targetTime) >= 0 if v, print ' "[path]" is newer than "[outName]"; will compile.' return true if v, print ' No source file is newer than "[outName]"; skipping compile.' return false def addRunTimeRef(opts as OptionValues) if not opts.containsKey('reference') opts['reference'] = List() refs = opts['reference'] to List libName = .backEnd.cobraRuntimeLibFileName if libName not in refs if .verbosity, print 'Adding reference to [libName]' refs.add(libName) def runProcess as Process return .runProcess(nil, nil) def runProcess(exeName as String?, argList as List?) as Process """ Returns a new Process with startInfo.fileName and p.startInfo.arguments set appropriately for the produced executable and the current options and any provided argsList. """ baseExeFileName = exeName ? .baseExeFileName fullExeFileName = exeName ? .fullExeFileName p = .backEnd.setupRunProcess(baseExeFileName, fullExeFileName) if argList and argList.count args = p.startInfo.arguments ? '' if args <> '', args += ' ' hasSpaces = any for a in argList where ' ' in a if hasSpaces argList = for arg in argList get if(' ' in arg, '"[arg]"', arg) args += argList.join(' ') p.startInfo.arguments = args return p ## ## ITypeProvider ## var _anyFloatType as AnyFloatType? var _anyIntType as AnyIntType? var _arrayTypes as Dictionary? var _boolType as BoolType? var _charType as CharType? var _decimalType as DecimalType? var _dynamicType as DynamicType? var _floatTypes = Dictionary() var _intTypes = Dictionary() var _nilType as NilType? var _numberType as AbstractNumberType? var _passThroughType as PassThroughType? var _unspecifiedType as UnspecifiedType? var _voidType as VoidType? var _nilableTypes as Dictionary? var _variTypes as Dictionary? var _nilableDynamicType as NilableType? def typeForName(name as String) as IType assert false, 'TODO' return .intType def typeOrNilForName(name as String) as IType? assert false, 'TODO' return nil get anyFloatType as AnyFloatType if _anyFloatType is nil _anyFloatType = AnyFloatType() return _anyFloatType to ! get anyIntType as AnyIntType if _anyIntType is nil _anyIntType = AnyIntType() return _anyIntType to ! def arrayType(type as IType) as ArrayType if _arrayTypes is nil, _arrayTypes = Dictionary() if _arrayTypes.containsKey(type) return _arrayTypes[type] else _arrayTypes[type] = at = ArrayType(type) at.bindInh at.bindInt return at get boolType as BoolType if _boolType is nil _boolType = BoolType() return _boolType to ! get charType as CharType if _charType is nil _charType = CharType() return _charType to ! get decimalType as DecimalType if _decimalType is nil _decimalType = DecimalType() return _decimalType to ! get dynamicType as DynamicType if _dynamicType is nil _dynamicType = DynamicType() return _dynamicType to ! def floatType as FloatType return .floatType(64) def floatType(size as int) as FloatType require size in [32, 64] type as FloatType? if _floatTypes.tryGetValue(size, out type) return type to ! else type = _floatTypes[size] = FloatType(size, .anyFloatType) return type to ! def intType as IntType return .intType(true, 32) def uintType as IntType return .intType(false, 32) def intType(signed as bool, size as int) as IntType require size in [8, 16, 32, 64] # CC: inherit from interface key = if(signed, -1, +1) * size type as IntType? if _intTypes.tryGetValue(key, out type) return type to ! else type = _intTypes[key] = IntType(signed, size, .anyIntType) return type to ! get nilType as NilType if _nilType is nil _nilType = NilType() return _nilType to ! get numberType as AbstractNumberType if _numberType is nil branch .options['number'] to String on 'decimal', _numberType = .decimalType on 'float', _numberType = .floatType on 'float32', _numberType = .floatType(32) on 'float64', _numberType = .floatType(64) else, throw FallThroughException(.options['number']) return _numberType to ! set numberTypeName as String """ Set `number` type from a string/name. """ require value in ['decimal', 'float', 'float32', 'float64'] branch value on 'decimal', _numberType = .decimalType on 'float', _numberType = .floatType on 'float32', _numberType = .floatType(32) on 'float64', _numberType = .floatType(64) else, throw FallThroughException(value) get passThroughType as PassThroughType if _passThroughType is nil _passThroughType = PassThroughType() return _passThroughType to ! get unspecifiedType as UnspecifiedType if _unspecifiedType is nil _unspecifiedType = UnspecifiedType() return _unspecifiedType to ! get voidType as VoidType if _voidType is nil _voidType = VoidType() return _voidType to ! def nilableType(type as IType) as NilableType if type inherits NilableType, return type if _nilableTypes is nil _nilableTypes = Dictionary() if _nilableTypes.containsKey(type) return _nilableTypes[type] else _nilableTypes[type] = nt = NilableType(type) nt.bindInh if not .isBindingInh, nt.bindInt return nt def variType(type as IType) as VariType if _variTypes is nil _variTypes = Dictionary() else if _variTypes.containsKey(type) return _variTypes[type] _variTypes[type] = vt = VariType(type) return vt def defaultType as IType return .nilableDynamicType def nilableDynamicType as NilableType if _nilableDynamicType is nil _nilableDynamicType = .nilableType(.dynamicType) return _nilableDynamicType to ! # -- end of ITypeProvider get typeType as IType return .libraryType('Type') ## More type stuff def readSystemTypes .backEnd.readSystemTypes def fixLibExtensions(references as List) i = 0 for reference in List(references) references[i] = .backEnd.fixLibExtension(references[i]) i += 1 def printRefs(references as List) if references.count == 0 print 'No additional assembly references.' else print 'Final assembly reference list:' i = 0 for refPath in references print '[i]. [refPath]' i += 1 def readAssemblyTypes(options as OptionValues) references = options.getStringList('reference') # Excluding the extension can be problematic .fixLibExtensions(references) # now that references are fixed, make a copy so that .options['references'] is not modified further references = List(references) if options.containsKey('pkg') for pkgName in options['pkg'] to List references.addRange(.refsForPackage(pkgName)) if .verbosity > 1 .printRefs(references) # Here be "reflectionOnlyLoad" code... which does not work on Mono 1.2.4 # The run-time error message says: # ** (./cobra.exe:24284): WARNING **: Cannot resolve dependency to assembly 'System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' because it has not been preloaded. When using the ReflectionOnly APIs, dependent assemblies must be pre-loaded or loaded on demand through the ReflectionOnlyAssemblyResolve event. # But hooking ReflectionOnlyAssemblyResolve has no effect as it never gets called. # Looks like others are having problems too: # http://csammisrun.net/shaim/viewtopic.php?t=29&sid=e46dc962b1e4d14a5210ae2852ac7d87 # General references: # * "Reflection Only Assembly Loading" blog entry by Junfeng Zhang # http://blogs.msdn.com/junfeng/archive/2004/08/24/219691.aspx # * http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2485940&SiteID=1 # Justin Pinnix from MSFT says "Once our assembly resolver finds your DLL, it calls # Assembly.LoadFrom(). So, even though your code calls Assembly.Load, we still # call Assembly.LoadFrom" # TODO: try this out on MS .NET and if it works there, use it there # TODO: file a Mono bug report (or ping an existing one) # curDomain = AppDomain.currentDomain # sharp'curDomain.ReflectionOnlyAssemblyResolve += _resolveEvent' for reference in references if not .loadReference(reference, false) _addMessage(SourceException('Cannot locate assembly reference "[reference]".')) def refsForPackage(pkgName as String) as List """ Returns the library/DLL refs for the give package name. Runs pkg-config to get that list. See HowTo/GTK.cobra """ refs = List() # example: pkg-config --libs gtk-sharp-2.0 p = Process() macMonoPkgConfig = '/Library/Frameworks/Mono.framework/Commands/pkg-config' if File.exists(macMonoPkgConfig) # avoid conflict with MacPorts whose pkg-config in '/opt/local/bin/pkg-config' will not pick up on Mono packages p.startInfo.fileName = macMonoPkgConfig else p.startInfo.fileName = 'pkg-config' p.startInfo.arguments = '--libs [pkgName]' if .verbosity > 1 print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' output = CobraCore.runAndCaptureAllOutput(p).trim if p.exitCode _addMessage(SourceException('Cannot locate package "[pkgName]"')) print ' $ [p.startInfo.fileName] [p.startInfo.arguments]' for line in output.split(c'\n') print ' |', line return refs parts = output.replace('-r:', '\0').split(c'\0') # while Cobra uses -ref:, pkg-config outputs -r: for part in parts if part.trim <> '', refs.add(part.trim) return refs def _resolveEvent(sender as Object, args as ResolveEventArgs) as Assembly? return nil var _referenceVerbosity = -1 get referenceVerbosity as int if _referenceVerbosity == -1 _referenceVerbosity = .options.getDefault('verbosity-ref', 0) return _referenceVerbosity var _willReadDependencies = false def indentPrint # see .loadReference which sets up the indented writer (CobraCore.printDestination to IndentedWriter).indent def outdentPrint # see .loadReference which sets up the indented writer (CobraCore.printDestination to IndentedWriter).dedent def loadReference(reference as String, addExtn as bool) as bool """ Attempts to load the given library reference. On success, adds reference to .loadedReferences and returns true. On failure, returns false, but does not add any warnings or errors. This method is used by Compiler and UseDirective. """ if addExtn reference = .backEnd.fixLibExtension(reference) v, rv = .verbosity, .referenceVerbosity if v, print 'Loading reference:', reference if rv print to IndentedWriter(CobraCore.printDestination, indentString=' : ') print '>> .loadReference("[reference]")' .indentPrint try r = _loadReference(reference) finally print 'Returning [r]' .outdentPrint print '<< .loadReference("[reference]")' else r = _loadReference(reference) if v and not r, print 'Could not load: ', reference return r def _loadReference(reference as String) as bool try return .backEnd.loadLibReference(reference) catch lre as LoadReferenceException # example: 'FileNotFoundException fileName Could not load file or assembly 'NHibernate, Version=2.1.0.1001, # Culture=neutral, PublcKeyToken=aa95f207798dfdb4' or one of its dependencies. The # system cannot find the file specified. if .referenceVerbosity, print '[lre] in _loadReference("[reference]")' return false def fixMemberSigs .backEnd.fixMemberSigs def installNativeMethods(box as Box, nativeType as NativeType) .backEnd.installNativeMethods(box, nativeType) ## ## Binding ## var _basicTypes as List? get basicTypes as IList if _basicTypes is nil _basicTypes = List() _basicTypes.addRange([.anyFloatType, .anyIntType, .boolType, .charType, .decimalType, .floatType]) for size in [32, 64] _basicTypes.add(.floatType(size)) for signed in [true, false] for size in [8, 16, 32, 64] _basicTypes.add(.intType(signed, size)) return _basicTypes to ! def symbolForName(name as String, haveThis as bool) as IMember? return .symbolForName(name, haveThis, false) def symbolForName(name as String, haveThis as bool, isLowerOkay as bool) as IMember? """ name - obvious. haveThis - if false, symbols like methods, properties, etc. will not be returned while enums, nested classes, etc. could be. isLowerOkay - true if a lowercase name (non capCased) is allowed. """ require name.length body # TODO: remove canBeMember arg assert _curModule # check for generic parameters in methods if not haveThis and not name[0].isLower if _codeMemberStack.count and _codeMemberStack.peek inherits Method m = (_codeMemberStack.peek to Method).genericParamForName(name) to IMember? if m, return m # check the current box which will ask its namespace which will ask its `use` directives if _boxStack.count #print '>> .compiler.symbolForName([name], [haveThis])' if not isLowerOkay and name[0].isLower assert false, 'use findLocal instead. [name]' return nil m = _boxStack.peek.symbolForName(name, haveThis) #print '<< .compiler.symbolForName returning', m #print if m is nil # global namespace of the current module m = ((_curModule to dynamic).topNameSpace).symbolForName(name) # TODO: cast above is kind of weird, but SharpModule has no .topNameSpace return m def dumpModulesForTestify(output as IndentedWriter) .dumpModulesForTestify(output, 'Modules') def dumpModulesForTestify(output as IndentedWriter, title as String) print '[title]:' output.indent try print to output i = 1 for module in _modules print '[i]. ' stop if module.isCobraLibrary print to output, module else module.writeDeepString(output) i += 1 finally output.dedent def _exitFromErrors # TODO 2009-12 Having problems with this requirement in same cases. Not important to enforce right now. # require .errors.count """ This method should not be invoked from partial classes found in phases or back-ends. Some phases want to run even when there are errors from previous phases. This is managed by the compiler's execution of the phases. """ .printMessages _deleteIntermediateFiles throw StopCompilation(this) def printMessages if .htmlWriter .printHtmlMessages else .printConsoleMessages Environment.exitCode = if(.errors.count, 1, 0) def printHtmlMessages require .htmlWriter dest = .htmlWriter to ! dest.writeHtml('[dest.newLine]\n') dest.writeHtml('\n') for msg in _messages msg.writeHtmlTo(dest) dest.writeHtml('
File Line Type Message
[dest.newLine]') if _errors.count dest.writeHtml('[_compilationFailedMessage()]
\n') # CC: axe ()s else dest.writeHtml('Compilation succeeded - [_warnings.count] warning[if(_warnings.count==1,'','s')]
\n') def printConsoleMessages willColor = .options.boolValue('color') savedColor = Console.foregroundColor if CobraCore.isRunningOnMono # bug on mono: the initial Console.foregroundColor is not necessarily accurate (at least in bash on Terminal.app on Mac OS X 10.4.10 on Mono 1.2.4) savedColor = ConsoleColor.Black # this is really just a guess and possibly a bad one. should be controllable via a cmd line option or argument dest = if(Console.error, Console.error, Console.out) to ! # some IDEs prefer errors and warnings to go to stderr for obj in _messages if willColor and obj.isError Console.foregroundColor = ConsoleColor.Red restoreColor = true print to dest, obj.consoleString if restoreColor Console.foregroundColor = savedColor restoreColor = false if _errors.count if willColor, Console.foregroundColor = ConsoleColor.Red print _compilationFailedMessage() # CC: axe ()s if willColor, Console.foregroundColor = savedColor else didPrint = false if willColor, Console.foregroundColor = ConsoleColor.Blue if _willPrintSuccessMsg or _warnings.count print 'Compilation succeeded' stop didPrint = true if _warnings.count print ' - [_warnings.count] warning[if(_warnings.count==1,'','s')]' else if didPrint, print if willColor, Console.foregroundColor = savedColor def _compilationFailedMessage as String return 'Compilation failed - [_errors.count] error[if(_errors.count==1,'','s')], [_warnings.count] warning[if(_warnings.count==1,'','s')]' def printTimingStats(elapsed as TimeSpan) secs = elapsed.totalMilliseconds / 1_000 if .linesCompiled lps = .linesCompiled / secs print '[.linesCompiled] lines compiled at [lps:F1] lines/sec' if .nodesCompiled nps = .nodesCompiled / secs print '[.nodesCompiled] nodes compiled at [nps:F1] nodes/sec' if .tokensCompiled tps = .tokensCompiled / secs print '[.tokensCompiled] tokens compiled at [tps:F1] tokens/sec' ## ## Source Code Corrections ## var _sourceCorrections = Dictionary>>() """ The first key of type String is filename. The second key of type int is line number. """ def correctSource(token as IToken, replace as String) if not _sourceCorrections.containsKey(token.fileName) _sourceCorrections[token.fileName] = Dictionary>() file = _sourceCorrections[token.fileName] if not file.containsKey(token.lineNum) file[token.lineNum] = List() file[token.lineNum].add(Replacement(token.colNum, token.text, replace)) def writeSourceCodeCorrections # TODO: this doesn't properly handle when the replacements are a different length than the # original next, but then this isn't needed right now since the only type of correction # being done is case correction if _sourceCorrections.count == 0, return v = .verbosity for fileName, lineToReplacements in _sourceCorrections if v, print 'Correcting source:', fileName lineNums = List(lineToReplacements.keys) lineNums.sort lines = File.readAllLines(fileName) for lineNum in lineNums for rep in lineToReplacements[lineNum] assert rep.oldText.length == rep.newText.length # see TODO above if v >= 2, print 'Correct: Line [lineNum], Column [rep.colNum], Old "[rep.oldText]", New "[rep.newText]"' i = lineNum - 1 line = lines[i] # print line oldLen = line.length col = rep.colNum - 1 lines[i] = line[:col] + rep.newText + line[col+rep.oldText.length:] # print lines[i] assert lines[i].length == oldLen try File.writeAllLines(fileName, lines) catch exc as Exception .warning(CobraWarning(fileName, nil, 'Cannot write source code corrections due to: [exc.message] ([exc.getType.name]).')) _sourceCorrections.clear ## ## Important system library types ## # ILibTypeProvider get objectType as IType # for ITypeProvider return _libraryClass('Object') def objectClass as Class # for stronger typing return _libraryClass('Object') def stringType as Class return _libraryClass('String') def exceptionType as Class return _libraryClass('Exception') def delegateType as Class return _libraryClass('Delegate') def attributeType as Box return _libraryBox('Attribute') def enumerableType as Box return _libraryBox('IEnumerable') def enumeratorType as Box return _libraryBox('IEnumerator') def enumerableOfType as Box return _libraryBox('IEnumerable') def enumeratorOfType as Box return _libraryBox('IEnumerator') def dictEnumeratorType as Box return _libraryBox('IDictionaryEnumerator') def collectionType as Box return _libraryBox('ICollection') def collectionOfType as Box return _libraryBox('ICollection') def ilistType as Box return _libraryBox('IList') def ilistOfType as Box return _libraryBox('IList') def listOfType as Class return _libraryClass('List') def idictionaryType as Box return _libraryBox('IDictionary') def idictionaryOfType as Box return _libraryBox('IDictionary') def dictionaryOfType as Class return _libraryClass('Dictionary') def setOfType as Class return _libraryClass('Set') def libraryType(qualifiedNameOrTag as String) as IType """ Implemented for ITypeProvider, but use the more specific methods such as .stringType instead. """ return _libraryType(.backEnd.resolveTypeTag(qualifiedNameOrTag)) var _libraryTypeCache = Dictionary() def _libraryType(qualifiedName as String) as IType """ find qualified Type name in (cache or) Library/namespace Decls structures. Used to retrieve types such as System.String. Example: _libraryType('System.String') """ assert qualifiedName.length type as IType? if _libraryTypeCache.tryGetValue(qualifiedName, out type) return type to ! names = qualifiedName.split(c'.') type = _libraryType(names to passthrough) _libraryTypeCache[qualifiedName] = type to ! return type to ! def _libraryType(names as vari String) as IType #trace names ns as IContainer = _globalNS thing as IContainer? = nil for name in names possible = (thing ? ns).declForName(name) #print name # dbg #if not possible # trace thing ? ns # ((thing ? ns) to NameSpace).dumpDeclsNameKeys assert possible, name if possible inherits IContainer thing = possible else assert false, [name, possible.getType.name, possible] if thing inherits IType if thing.name<>names[names.length-1] # TODO: add this as an ensure as well print 'names=' stop print CobraCore.toTechString(names) print 'thing=[thing]' assert false return thing else throw FallThroughException('found [name], but it is not an IType. it is [thing]') def _libraryBox(qualifiedNameOrTag as String) as Box """ Returns a box from the standard library such as 'System.Object' or 'System.Collections.Generic.IEnumerable'. """ return _libraryType(.backEnd.resolveTypeTag(qualifiedNameOrTag)) to Box def _libraryClass(qualifiedNameOrTag as String) as Class """ Returns a class from the standard library such as 'System.Object' or 'System.Collections.Generic.Dictionary'. """ return _libraryType(.backEnd.resolveTypeTag(qualifiedNameOrTag)) to Class ## ## Services to nodes ## def embedRunTimeSuffix as String return .options.embedRunTimeSuffix #special type proxies for common Types - Object and Type def objectTypeProxy as AbstractTypeProxy """ Type proxy for BE notion of a class for the root of Object hierarchy e.g. Object in dotNet """ #return ClrTypeProxy(Object) return .backEnd.objectTypeProxy def typeTypeProxy as AbstractTypeProxy """ Type proxy for BE notion of a class for a Type e.g. System.Type in dotNet, java.lang.class in Java """ #return ClrTypeProxy(Type) return .backEnd.typeTypeProxy def nativeType(qualifiedName as String) as NativeType return .backEnd.nativeTypeByName(qualifiedName) def suggestionFor(name as String) as String? is shared require name.length # CC: return _unknownSuggestions.getDefault(name, nil) if _unknownSuggestions.containsKey(name) return _unknownSuggestions[name] else if _unknownSuggestions.containsKey(name.toLower) return _unknownSuggestions[name.toLower] else return nil def warning(node as ISyntaxNode, msg as String) require msg.length .warning(CobraWarning(node.token, msg)) def warning(cw as CobraWarning) require not cw.isError if _suppressWarning(cw), return _addMessage(cw) def _suppressWarning(cw as CobraWarning) as bool # TODO: add suppress all warnings (cmdline sw) - maybe? # TODO: add suppress by error tag - maybe? entry = if(cw.hasSourceSite, '[cw.fileName]:[cw.lineNum]', '') return _noWarningLines.contains(entry) def augmentWarning(node as ISyntaxNode, lookFor as String, search as String, augment as String) as bool require lookFor.length augment.length body message = CobraWarning(node.token, lookFor) key = if(message.hasSourceSite, '[message.fileName]:[message.lineNum]', '') if not _messagesPerSourceLine.containsKey(key) return false lowerToMatch = message.message.toLower lowerSearch = if(search.length > 0, search.toLower, '') for se in _messagesPerSourceLine[key] lowerMsg = se.message.toLower if lowerMsg.contains(lowerToMatch) # matched if search.length == 0 or lowerMsg.contains(lowerSearch) se.appendToMessage(augment) return true return false shared var _unknownSuggestions = { # literals 'null': 'nil', 'none': 'nil', 'nothing': 'nil', 'True': 'true', 'False': 'false', # Python 'self': 'this', 'super': 'base', 'elif': 'else if', # Ruby 'puts': 'print', # C# 'using': 'use', 'foreach': 'for', # VisualBasic 'imports': 'use', 'sub': 'def', # operators (word ones anyway) 'isa': 'inherits', 'new': 'SomeClass() or SomeClass(arg1,arg2) without new', # constructs #'lambda': 'def(args) ...', # TODO # statements 'do': 'post while ', 'elseif': 'else if', # types 'boolean': 'bool', 'character': 'char', 'byte': 'uint8', 'sbyte': 'int8', 'short': 'int16', 'ushort': 'uint16', 'integer': 'int', 'long': 'int64', 'ulong': 'uint64', 'single': 'float32', 'double': 'float', 'str': 'String', 'string': 'String', 'object': 'Object', } get unknownSuggestions from var def suggestionForUnknown(word as String) as String? if _unknownSuggestions.tryGetValue(word, out word), return word else, return nil ## ## Services to this ## def _addMessage(message as SourceException) # Cobra can sometimes generate duplicate messages. Specifically, a constructed type might generate a message that its generic def did. # Also, assert statements whose conditions cause C# warnings will get duplicate warnings because of the expression breakdown. # Yeah, ideally, this would be fixed, but sometimes that's harder than just removing the duplicates: key = if(message.hasSourceSite, '[message.fileName]:[message.lineNum]', '') if _messagesPerSourceLine.containsKey(key) for se in _messagesPerSourceLine[key] if se.message.toLower == message.message.toLower if Utils.isDevMachine or .verbosity >= 3 print 'Skipping duplicate message:', message.consoleString return else _messagesPerSourceLine[key] = List() _messagesPerSourceLine[key].add(message) _messages.add(message) if message.isError _errors.add(message) else _warnings.add(message) def initBackEnd require .backEnd is nil ensure .backEnd branch .options.get('back-end') on 'none', _backEnd = ClrBackEnd(this) # TODO-SELFHOST on 'clr', _backEnd = ClrBackEnd(this) on 'jvm' _backEnd = JvmBackEnd(this) if .options['number'] == 'decimal', .options['number'] = 'float' on 'objc', _backEnd = ObjcBackEnd(this) else, throw FallThroughException(.options.get('back-end')) class InternalError inherits SourceException """ Represents cases where the Cobra compiler itself has experienced an internal exception. """ var _fileName as String? var _lineNum as int? var _internalException as Exception cue init(fileName as String, lineNum as int, message as String, internalException as Exception) .init(message, internalException) _fileName = fileName _lineNum = lineNum cue init(message as String, internalException as Exception) base.init(('COBRA INTERNAL ERROR / ' + internalException.getType.name + ' / ' + message.replace('\r','').replace('\n','; ').trim)[:1024]) _internalException = internalException get isError as bool is override return true get hasSourceSite as bool is override return _fileName is not nil get fileName as String is override return _fileName to ! get lineNum as int is override return _lineNum to ! class Replacement cue init(colNum as int, oldText as String, newText as String) base.init _colNum = colNum _oldText = oldText _newText = newText get colNum from var as int get oldText from var as String get newText from var as String class TestCompiler inherits Compiler """ Used by SharpCompilationMessage.test CC: should be inside the test or easy to remove via a test attribute TODO: should inherit an AbstractCompiler """ cue init base.init(0) _backEnd = BasicBackEnd(this) pro verbosity as int is override get return 0 set pass def cobraLineNumForCurly(fileName as String, lineNum as int) as int is override return lineNum