""" References: Reflection-only loading of assemblies http://blogs.msdn.com/b/junfeng/archive/2004/08/24/219691.aspx """ class Compiler is partial var _didLoadAssemblies = Set() var _didReadAssemblies = Set() var _clrTypeToType = Dictionary() var _clrTypeCache = Dictionary() def clrLoadReference(reference as String) as String? try return _clrLoadReference(reference) catch fnfe as System.IO.FileNotFoundException throw LoadReferenceException('FileNotFound', fnfe.fileName, fnfe.message) catch fle as System.IO.FileLoadException throw LoadReferenceException('FileLoadException', fle.fileName ? reference, fle.message) def _clrLoadReference(reference as String) as String? """ uses compiler .referenceVerbosity, _willReadDependencies fills compiler .loadedReferences """ require reference.endsWith('.dll') or reference.endsWith('.exe') reference not in ['.dll', '.exe'] ensure not result implies .loadedReferences[.loadedReferences.count-1] == reference result implies .loadedReferences.count == old .loadedReferences.count body rv = .referenceVerbosity assert not (reference.startsWith('-r') and '-r' in reference[2:]) couldNotFindMsg = 'Could not find file.' if File.exists(reference) # try current directory if rv, print '"[reference]" found as file. Will _loadAssemblyFileNamed().' referredAss = _loadAssemblyFileNamed(reference) if rv, print '_loadAssemblyFileNamed() returned: [CobraCore.toTechString(referredAss)]' else # TODO: the problem with -lib: in both Cobra and C# is that it has no effect on runtime, # you must still register the DLLs in the GAC or copy them into the same dir as the .exe # because the -lib: paths are not passed into executable. # So should Cobra copy the .dll's into the target directory (if their paths are not in MONO_PATH)? if rv, print 'File does not exist.' searchPaths = .options.getDefault('library-directory', List()) to List found = false for searchPath in searchPaths if rv, print 'Checking lib path: "[searchPath]"' combinedPath = Path.combine(searchPath, reference) if File.exists(combinedPath) if rv, print '"[reference]" found as file. Will _loadAssemblyFileNamed().' referredAss = _loadAssemblyFileNamed(combinedPath) if rv, print '_loadAssemblyFileNamed() returned: [CobraCore.toTechString(referredAss)]' found = true break if rv if searchPaths.count if not found, print 'Did not find "[reference]" in lib paths' else print 'No lib paths to search.' if not found and not reference.hasSlash # try system wide (GAC) if reference.endsWith('.dll'), reference = reference[:-4] if rv, print 'Will load with partial name "[reference]"' referredAss = _loadAssemblyWithSimpleName(reference) if rv, print 'Load with partial name returned: [CobraCore.toTechString(referredAss)]' if referredAss is nil, return couldNotFindMsg reference += if(referredAss.location.endsWith('.exe'), '.exe', '.dll') if referredAss if _willReadDependencies # wired false in Compiler.cobra for dependency in referredAss.getReferencedAssemblies if rv print '>> Loading dependency: [dependency]' .indentPrint try _loadAssemblyNamed(dependency) finally .outdentPrint print '<< Loading dependency: [dependency]' else _loadAssemblyNamed(dependency) if rv, print 'Will read assembly: [referredAss]' try _readAssembly(referredAss, reference <> 'Cobra.Core.dll') catch readExc as Exception if rv, print 'Caught exception during read assembly: [readExc]' throw if rv, print 'Did read assembly: [referredAss]' # reassert the preconditions. there have been bugs in the past assert reference.endsWith('.dll') or reference.endsWith('.exe') assert reference not in ['.dll', '.exe'] .loadedReferences.add(reference) if rv, print 'Returning nil (=okay) for __dotNetLoadReference("[reference]").' return nil else if rv, print 'Returning "[couldNotFindMsg]" for __dotNetLoadReference("[reference]").' return couldNotFindMsg def _loadAssemblyNamed(assName as AssemblyName) # to-do: retire this? throw Exception('not expecting this to be called') if .referenceVerbosity print '>> loadAssembly [assName]' .indentPrint try __loadAssemblyNamed(assName) finally .outdentPrint print '<< loadAssembly [assName]' else __loadAssemblyNamed(assName) def __loadAssemblyNamed(assName as AssemblyName) rv = .referenceVerbosity - 1 if rv < 0, rv = 0 if assName.toString in _didLoadAssemblies if rv, print 'Already loaded.' return _didLoadAssemblies.add(assName.toString) try if rv, print 'Will Assembly.load([assName])' ass = Assembly.load(assName) # Will not pick up the assembly from the same directory. if rv, print 'Did Assembly.load with result: [ass]' catch fnfe as FileNotFoundException # In fact, will get a FileNotFoundException! if rv, print 'Did Assembly.load with exception: [fnfe]. Will try .loadReference' .loadReference(assName.name to !, true) # TODO: compare the the name loaded to the name given with AssemblyName.referenceMatchesDefinition return catch exc as Exception msg = 'Could not open assembly "[assName]" due to: [exc.getType.name]: [exc.message]' if rv, print msg throw SourceException(msg) if _willReadDependencies # wired false in Compiler.cobra for dependency in ass.getReferencedAssemblies if rv print '>> Loading dependency: [dependency]' .indentPrint try _loadAssemblyNamed(dependency) finally .outdentPrint print '<< Loading dependency: [dependency]' try .clrReadAssembly(ass to !) catch readExc as Exception if rv, print 'Caught exception during read assembly: [readExc]' throw var __willLoadReflectionOnly as bool? def _willLoadReflectionOnly as bool if __willLoadReflectionOnly is nil __willLoadReflectionOnly = (Environment.getEnvironmentVariable('COBRA_REFLECTION_ONLY') ? '').trim in ['1', 'true', 'yes'] if .referenceVerbosity > 0, print 'Reflection-only:', __willLoadReflectionOnly return __willLoadReflectionOnly to ! var __didInitForReflectionOnlyLoading = false def _loadAssemblyFileNamed(assemblyFileName as String) as Assembly? require assemblyFileName.length > 0 not assemblyFileName.contains('Culture=') not assemblyFileName.contains('PublicKeyToken=') File.exists(assemblyFileName) body if _willLoadReflectionOnly if not __didInitForReflectionOnlyLoading __didInitForReflectionOnlyLoading = true curDomain = AppDomain.currentDomain listen curDomain.reflectionOnlyAssemblyResolve, ref _onReflectionOnlyAssemblyResolve return Assembly.reflectionOnlyLoadFrom(assemblyFileName) else return Assembly.loadFrom(assemblyFileName) def _onReflectionOnlyAssemblyResolve(sender, args as ResolveEventArgs) as Assembly? assName = args.name to ! rv = .referenceVerbosity - 1 if rv < 0, rv = 0 if assName in _didLoadAssemblies if rv, print 'Already loaded.' return nil _didLoadAssemblies.add(args.name to !) try if rv, print 'Will Assembly.load([assName])' ass = Assembly.reflectionOnlyLoad(assName) if rv, print 'Did Assembly.load with result: [ass]' catch fnfe as FileNotFoundException if rv, print 'Did Assembly.load with exception: [fnfe]. Will try .reflectionOnlyLoad' return Assembly.reflectionOnlyLoadFrom(assName) catch exc as Exception msg = 'Could not open assembly "[assName]" due to: [exc.getType.name]: [exc.message]' if rv, print msg throw SourceException(msg) return ass def _loadAssemblyWithSimpleName(name as String) as Assembly? require name.length > 0 not name.hasSlash not name.endsWith('.dll') and not name.endsWith('.exe') not name.contains('Culture=') and not name.contains('PublicKeyToken=') body if _willLoadReflectionOnly # find this in the GAC path = _pathForGacAssemblyWithSimpleName(name) if path, return _loadAssemblyFileNamed(path) else, return nil else # TODO: Holy crap! loadWithPartialName is marked obsolete, # but I know of no other way to support a library reference like "compiler /r:System.Data.dll myprog.ext" as C#, Cobra, etc. do. # Suggestions/solutions are welcome. # avoid warning: "System.Reflection.Assembly.LoadWithPartialName(string)" is obsolete # return Assembly.loadWithPartialName(name) # with dynamic binding: assemblyType = Assembly to dynamic try ass = assemblyType.loadWithPartialName(name) catch System.Reflection.TargetInvocationException # or any Exception ass = nil return ass var _gacPath as String? def _pathForGacAssemblyWithSimpleName(name as String) as String? require name.length > 0 not name.hasSlash not name.endsWith('.dll') and not name.endsWith('.exe') not name.contains('Culture=') and not name.contains('PublicKeyToken=') ensure result implies result.endsWith('.dll') body # only tested on Mono 2.10 + CLR 4 so far rv = .referenceVerbosity if _gacPath is nil # find the gac a = Assembly.getAssembly(System.Uri) # System.Uri is in System.dll # a=System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 # a.location='/usr/lib/mono/gac/System/2.0.0.0__b77a5c561934e089/System.dll' sep = Path.directorySeparatorChar _gacPath = a.location.split('[sep]System[sep]')[0] if rv > 0, print 'Found gac at:', _gacPath # ex: /usr/lib/mono/gac path = Path.combine(_gacPath, name) if rv > 0, print 'Checking:', path # ex: # ex: /usr/lib/mono/gac/System.Data if not Directory.exists(path) if rv > 0, print 'Not found.' return nil subdirs = DirectoryInfo(path).getDirectories.toList # ex: 4.0.0.0__b77a5c561934e089 2.0.0.0__b77a5c561934e089 subdirs.sort(do(d1 as DirectoryInfo, d2 as DirectoryInfo)=d2.name.compareTo(d1.name)) for subdir in subdirs if subdir.name[0].isDigit if rv > 0, print 'Found:', subdir.name path = Path.combine(subdir.fullName, name+'.dll') if rv > 0, print 'Full path:', path if not File.exists(path) if rv > 0, print 'Full path not found.' else return path if rv > 0, print 'Could not find in gac:', name return nil def clrReadAssembly(ass as Assembly) """ Reads the contents of an dotNet assembly (.DLL) so that they are accessible to the program. Called by ClrTypeProxy. """ assert _backEnd inherits ClrBackEnd _readAssembly(ass, false) # ScanClrType def _readAssembly(ass as Assembly, skipCobra as bool) """ Reads the contents of an assembly (.DLL) so that they are accessible to the program. In other words, this method reads libraries. """ if ass.getName.toString in _didReadAssemblies, return _didReadAssemblies.add(ass.getName.toString) verbosity = .verbosity if verbosity print 'Reading assembly: [ass]' # extra space lines up with 'Loading reference:' print ' at: [ass.location]' namespaceQualNameToNameSpaceObject = Dictionary() module = AssemblyModule(ass.location, .globalNS) #module = AssemblyModule(ass, .globalNS) saveModule, _curModule = _curModule, module try _modules.add(module) skipCobra = false for type in ass.getExportedTypes if type.isNested or type.declaringType # these will be scanned by Box._scanNestedTypes # print '### skipping [type.name] in [type.namespace]. isNested=[type.isNested], declaringType=[type.declaringType]' continue typeNamespace = type.namespace if typeNamespace is nil or typeNamespace.length == 0 # happens for classes etc. that are not declared in a namespace curNameSpace = module.topNameSpace else namespaceName = typeNamespace to ! if namespaceQualNameToNameSpaceObject.containsKey(namespaceName) curNameSpace = namespaceQualNameToNameSpaceObject[namespaceName] else curNameSpace = module.topNameSpace for name in typeNamespace.split(c'.') curNameSpace = curNameSpace.getOrMakeNameSpaceNamed(Token.empty, name) assert not curNameSpace.isUnified namespaceQualNameToNameSpaceObject[namespaceName] = curNameSpace if verbosity >= 4 print ' Reading type [type.name] in namespace "[namespaceName]"' clrType = ClrNativeType(type) if curNameSpace.unifiedNameSpace.declForName(clrType.name) # Happens between these two assemblies for *dozens* of types: # Reading assembly: office, Version=11.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c # at: C:\WINDOWS\assembly\GAC\office\11.0.0.0__71e9bce111e9429c\office.dll # Reading assembly: Interop.Microsoft.Office.Core, Version=2.4.0.0, Culture=neutral, PublicKeyToken=null # at: C:\Documents and Settings\Chuck\My Documents\Kaiser\Workspaces\SpiritDevA\Code\Cobra\Interop.Microsoft.Office.Core.d .warning(CobraWarning('Already have declaration "[clrType.name]" in namespace "[curNameSpace.fullName]".')) else if type.isClass # to-do: should be using a custom attribute instead of examining the name name = type.name if name.startsWith('Extend_') and name.count(c'_') >= 2 and '_Tests_' not in name curNameSpace.addDecl(Extension(clrType, .backEnd)) else curNameSpace.addDecl(Class(clrType, .backEnd)) else if type.isInterface curNameSpace.addDecl(Interface(clrType, .backEnd)) else if type.isEnum curNameSpace.addDecl(EnumDecl(curNameSpace, clrType, List(), '')) # TODO: isNames; docString? else if type.isValueType curNameSpace.addDecl(Struct(clrType, .backEnd)) else if type.isAnsiClass # The Enum class is an example that returns false for .isClass but true for .isAnsiClass curNameSpace.addDecl(Class(clrType, .backEnd)) else throw FallThroughException(type) finally _curModule = saveModule def dotNetFixNilableMemberSigs # Fix the listed members to be non Nilable # TODO: this really needs to go in a separate file that the compiler reads each time # TODO: look to see if what the Spec# team put together can be leveraged instead of recreating all this work! # hard coded below. TODO: read from a Cobra config file _fix('System.Object', 'toString getType memberwiseClone') # ^ regarding .toString, not technically true, but common enough and life is too painful when the return type is nilable _fix('System.Console', 'out') _fix('System.String', 'padLeft padRight remove replace substring toLower toUpper trim trimEnd trimStart') _fix('System.Type', 'assembly name toString') # namespace can return nil if the Type is a generic parameter _fix('System.Environment', 'commandLine currentDirectory newLine version') _fix('System.Exception', 'message') _fix('System.Collections.Generic.IEnumerable', r'getEnumerator') _fix('System.Collections.Generic.IList', r'[] getRange toArray') _fix('System.Collections.Generic.List', r'[]') _fix('System.Collections.Generic.IDictionary', r'[] keys values') _fix('System.Collections.Generic.Dictionary', r'[]') _fix('System.Collections.Generic.KeyValuePair', r'key value') _fix('System.Globalization.CultureInfo', 'invariantCulture') _fix('System.IO.File', 'create createText open openRead openText openWrite readAllBytes readAllLines readAllText') _fix('System.IO.FileSystemInfo', 'name fullName') _fix('System.IO.TextWriter', 'newLine') _fix('System.IO.Path', 'combine getFullPath') # getDirectoryName does return String? # getFileName does return String? # TODO: Add something like CobraUtils.getDirectoryName and .getFileName that return String instead # args: System.IO.Path.combine(arg1 as String, arg2 as String) as String _fix('System.Text.StringBuilder', 'toString') _fix('System.Text.RegularExpressions.Regex', 'match replace') _fix('System.Diagnostics.Process', 'processName') _fix('System.Reflection.Assembly', 'getEntryAssembly getExecutingAssembly location') _fix('System.Reflection.MemberInfo', 'name') _fix('System.Reflection.FieldInfo', 'fieldType') _fix('System.Reflection.ParameterInfo', 'parameterType') _fix('System.Reflection.PropertyInfo', 'propertyType') # TODO: I don't think sigs outside the "standard lib" can be specified # HttpUtility.htmlEncode htmlDecode urlEncode urlDecode # TODO: shouldn't need the following. see comment in _fixSubs _fix('System.IO.StringWriter', 'toString') def _fix(className as String, memberNames as String) type = .libraryType(className) to ? if type is nil print 'WARNING: Cannot find [className].' # TODO: make a real warning else if type inherits Box type.membersToUnNil = memberNames else print 'WARNING: Cannot fix [className] which is not a class/struct/interface. (type=[type])' # TODO: make a real warning def typeForClrType(clrType as System.Type) as IType? """ Returns the Cobra type for a CLR type if the CLR type was previously scanned. In other words, this is access to the clr Type cache. This not only eliminates unwanted duplication, but it is essential to the compiler when it checks for certain things like "does this type implement IDictionary?" """ return if(_clrTypeToType.containsKey(clrType), _clrTypeToType[clrType], nil) def addTypeForClrType(type as IType, clrType as System.Type) require .typeForClrType(clrType) in [nil, type] _clrTypeToType[clrType] = type def clrTypeByName(qualifiedName as String) as System.Type """ Obtain the CLR type given by the fully qualified name. This is for internal use of the compiler, not for binding type names found in source code. Passing an unknown name will throw an exception. Eventually this method will be key in targeting different versions of the CLR. Suppose you are on .NET 4.0 and want to target .NET 3.5. """ t as System.Type? _clrTypeCache.tryGetValue(qualifiedName, out t) if t, return t u = _clrTypeCache[qualifiedName] = System.Type.getType(qualifiedName) return u def installClrNativeMethods(box as Box, nativeType as ClrNativeType) meths = List() _installClrNativeConstantsFrom(nativeType, box) _installClrNativeMethodsFrom('System', nativeType, nativeType, box, meths) _installClrNativeMethodsFrom('System', ClrNativeType(System.Math.getType), nativeType, box, meths) # the next statement can be problematic in that you have to add new DecimalTools methods in Snapshot just so the compiler can see them # ultimately, extensions of primitive types should be supported _installClrNativeMethodsFrom('CobraCoreInternal', ClrNativeType(DecimalTools.getType), nativeType, box, meths) # _printMethods(meths, box) def compareMethodNames(a as Method, b as Method) as int return a.name.compareTo(b.name) def _printMethods(meths as IList, box as Box) # print out the methods, useful for documentation print print 'type', box.name sharedMeths = for meth in meths where meth.isShared if sharedMeths.count sharedMeths.sort(ref .compareMethodNames) print ' shared' for meth in sharedMeths print ' [meth.cobraSourceSignature(false)]' objectMeths = for meth in meths where not meth.isShared if objectMeths.count objectMeths.sort(ref .compareMethodNames) for meth in objectMeths print ' [meth.cobraSourceSignature]' def _installClrNativeConstantsFrom(nativeType as ClrNativeType, box as Box) for fieldInfo in nativeType.backEndType.getFields(BindingFlags(DeclaredOnly, Static, Public)) if fieldInfo.isSpecialName, continue if fieldInfo.isAssembly, continue if fieldInfo.isPrivate, continue name = Utils.cobraNameForNativeMemberName(fieldInfo.name) type = ClrTypeProxy(fieldInfo.fieldType) attrs = AttributeList() isNames = [if(fieldInfo.isPublic, 'public', 'protected')] # private was guarded against above modifiers = List() if fieldInfo.isStatic, modifiers.add('shared') if fieldInfo.isInitOnly or fieldInfo.isLiteral modifiers.add('nonvirtual') value = fieldInfo.getValue(nil) initExpr = SharpExpr(TokenFix.empty, if(value, .toString, 'nil')) # DocGenerator.cobra uses this boxConst = BoxConst(TokenFix.empty, TokenFix.empty, box, name, type, isNames, initExpr, attrs, '') boxConst.binaryName = fieldInfo.name box.addDecl(boxConst) def _installClrNativeMethodsFrom(namespaceName as String, nativeType as NativeType, thisNativeType as NativeType, box as Box, meths as IList) argClrType = (nativeType to ClrNativeType).backEndType thisClrType = (thisNativeType to ClrNativeType).backEndType # print # print '** [.name], clrType' isSameNativeType = argClrType == thisClrType for methInfo in argClrType.getMethods(BindingFlags(DeclaredOnly, Static, Instance, Public)) if methInfo.isSpecialName, continue # print # print '--', methInfo, methInfo.isStatic name = Utils.cobraNameForNativeMemberName(methInfo.name) modifiers = List() if methInfo.isStatic, modifiers.add('shared') newParams = List() first = true cancel = false for nativeParam in methInfo.getParameters if first first = false if methInfo.isStatic if methInfo.getParameters[0].parameterType == thisClrType # When the first arg of the shared method is the same type, # then make an instance method that can be used directly on values of the type. # print methInfo.getParameters # print methInfo.getParameters[0].parameterType modifiers.remove('shared') continue else if not isSameNativeType # don't want, for example, Math.sign showing up in types char, bool, etc. as a shared method cancel = true continue param = Param(Token.empty.copy('ID', nativeParam.name ? ''), ClrTypeProxy(nativeParam.parameterType)) if nativeParam.parameterType.isByRef and not nativeParam.isOut param.direction = Direction.InOut else if nativeParam.isOut param.direction = Direction.Out newParams.add(param) if cancel, continue meth = Method(Token.empty, Token.empty, box, name, newParams, ClrTypeProxy(methInfo.returnType), nil, modifiers, AttributeList(), '') if methInfo.isStatic meth.sharedMethodBacking = '[namespaceName].[argClrType.name].[methInfo.name]' if 'shared' in modifiers, meth.sharedMethodBackingIsAlias = true overload = nil to MemberOverload? other = box.declForName(name) if other if other inherits MemberOverload overload = other else if other implements IOverloadable overload = MemberOverload(other) box.registerOverload(overload to !) else throw FallThroughException([box, meth, other]) else assert box.declForName(name) is nil if overload overload.addMember(meth) else box.addDecl(meth) meths.add(meth) class Box is partial get _clrType as Type """ Returns the CLR System.Type boxed by the .nativeType. Throws exception rather than return nil. """ return (.nativeType to ClrNativeType).clrType var _defaultMemberName as String? """ An Indexer in CLR is known by being a property with arguments whose name matches the .memberName of a box-level DefaultMemberAttribute. """ def isClrSystemExceptionClass as bool return .name == 'Exception' and .parentNameSpace and .parentNameSpace.fullName == 'System' def prepSystemObjectClassClr # Pretend .NET is a bit more OO, consistent and elegant. # C#'s typeof(X) is X.getType in Cobra. existing = .declForName('getType') to AbstractMethod overload = MemberOverload(existing) .registerOverload(overload) meth = Method(TokenFix.empty, TokenFix.empty, this, 'getType', List(), .compiler.typeTypeProxy, nil, ['shared'], AttributeList(), 'Returns the Type instance that defines this type.') meth.sharedMethodBacking = 'typeof' overload.addMember(meth) # Enumeration lookup def getEnumeratorMemberClr as IMember? """ Step1 of 2 step lookup for Enumerator type - return getEnumeratorMember for current box """ getEnum as IMember? if .declForName('getEnumerator') is nil # Comes up for IList which has multiple 'getEnumerator' methods in ancestor interfaces for member in .allMembersForName('getEnumerator') if member inherits Method and member.parentBox.isGeneric getEnum = member break if getEnum is nil getEnum = .symbolForName('getEnumerator', true) if getEnum assert getEnum.didBindInt # can have two getEnumerators -- one generic and the other not. favor the generic one if getEnum inherits MemberOverload for member in getEnum.members if member inherits Method if member.resultType <> .compiler.objectType # implementing IEnumerable which requires two `getEnumerator` members getEnum = member break return getEnum def getEnumeratorMemberTypeClr(rt as IType) as IType? """ Step2 of 2 step lookup for Enumerator type - return the type for Enumerator member for current box rt is the resultType from getEnumerator lookup above - i.e the Enumerator for this Box """ if rt inherits Box and (rt to Box).isGeneric # don't take the first argument of the result type -- that won't work for a nested type in a generic class, like ValueCollection, which gets the generic params of its parents return rt.memberForName('current').resultType else if rt.isDescendantOf(.compiler.dictEnumeratorType) return rt.memberForName('entry').resultType if rt.isDescendantOf(.compiler.enumeratorType) rt = rt.memberForName('current').resultType if rt.nonNil.isSystemObjectClass # can we do better with indexer returnType? (e.g. MatchCollection) indexer = .symbolForName(r'[]', true) if indexer, rt = indexer.resultType return rt else throw FallThroughException({'rt': rt, 'this': this }) return nil # dll scanning def scanNativeTypeClr """ Subclasses should invoke base and then invoke the various _scanFoo methods that are appropriate for them. """ ensure not .needScanNativeType _needScanNativeType = false # print '<> _scanNativeType for [.name] in [_parentNameSpace.fullName], class is [.getType.name]' def scanGenericArgsClr if _clrType.isGenericType for genArg in _clrType.getGenericArguments t = (.compiler to Compiler).typeForClrType(genArg) if t is nil t = GenericParam(ClrNativeType(genArg), parentDefinition=this) (.compiler to Compiler).addTypeForClrType(t to !, genArg) _genericParams.add(t) def _scanClrIsNames # TODO _isNames.add('extern') # to make the box like the ones that were in SystemInterfaces.cobra # scan DefaultMemberAttribute for later use for attr in _clrType.getCustomAttributes(true) if attr inherits DefaultMemberAttribute _defaultMemberName = attr.memberName # this attribute names the indexer for the class break def _scanClrImplements for interf in _clrType.getInterfaces if not _badClrRelatedType(interf) _baseInterfaceProxies.add(ClrTypeProxy(interf)) def _scanClrNestedTypes # TODO: enable and fix resulting bugs # for type in .clrType.getNestedTypes(BindingFlags(Instance, Static, DeclaredOnly, Public, NonPublic)) for type in _clrType.getNestedTypes _scanClrNestedType(type) for type in _clrType.getNestedTypes(BindingFlags(Static, DeclaredOnly, NonPublic)) if type.isEnum and not type.isPublic and not type.isNestedAssembly # "not type.isNestedAssembly" guards against a strange WPF enum on Windows called # Control+ControlBoolFlags whose Enum.getValues() returns enums instead of ints _scanClrNestedType(type) def _scanClrNestedType(type as System.Type) clrType = ClrNativeType(type) if type.isClass .addDecl(Class(clrType, .backEnd)) else if type.isInterface .addDecl(Interface(clrType, .backEnd)) else if type.isEnum .addDecl(EnumDecl(this, clrType, List(), '')) # TODO: isNames; docString? else if type.isValueType .addDecl(Struct(clrType, .backEnd)) else if type.isAnsiClass # The Enum class is an example that returns false for .isClass but true for .isAnsiClass .addDecl(Class(clrType, .backEnd)) else throw FallThroughException(type) lastDecl = .declsInOrder[.declsInOrder.count-1] to dynamic if (lastDecl to Object).getType.getProperty('ParentBox') # CC: if lastDecl responds to (get parentBox as Box?) lastDecl.parentBox = this def _scanClrFields for fieldInfo in _clrType.getFields(BindingFlags(Instance, Static, DeclaredOnly, Public, NonPublic)) if fieldInfo.declaringType is not _clrType, continue if fieldInfo.isAssembly, continue if fieldInfo.isPrivate, continue name = Utils.cobraNameForNativeMemberName(fieldInfo.name) type = _clrMemberTypeResultProxy(fieldInfo, fieldInfo.fieldType) attrs = AttributeList() isNames = [if(fieldInfo.isPublic, 'public', 'protected')] # private was guarded against above if fieldInfo.isStatic, isNames.add('shared') if fieldInfo.isInitOnly or fieldInfo.isLiteral # to-do: technically this is picking up 'readonly' as well isNames.add('nonvirtual') value = nil try if not _clrType.assembly.reflectionOnly _ and fieldInfo.isStatic _ and not fieldInfo.fieldType.containsGenericParameters # then value = fieldInfo.getValue(nil) catch InvalidOperationException # Unhandled Exception: System.InvalidOperationException: # Late bound operations cannot be performed on fields with types for which Type.ContainsGenericParameters is true. # ICSharpCode.AvalonEdit.Utils.ImmutableStack`1[T] Empty (RtFieldInfo) pass initExpr = SharpExpr(TokenFix.empty, if(value, .toString, 'nil')) # DocGenerator.cobra uses this boxConst = BoxConst(TokenFix.empty, TokenFix.empty, this, name, type, isNames, initExpr, attrs, '') boxConst.binaryName = fieldInfo.name .addDecl(boxConst) else varr = BoxVar(TokenFix.empty, TokenFix.empty, this, name, type, isNames, nil, attrs, '') varr.binaryName = fieldInfo.name .addDecl(varr) def _scanClrInitializers for conInfo in _clrType.getConstructors(BindingFlags(Instance, DeclaredOnly, Public, NonPublic)) if conInfo.isPrivate, continue if conInfo.declaringType is not _clrType, continue skip = false for paramInfo in conInfo.getParameters if _badClrRelatedType(paramInfo.parameterType) skip = true break if skip, continue params = _scanClrParams(conInfo.getParameters) isNames = _isNamesForMethodInfo(conInfo) attribs = _attribsForMethodInfo(conInfo) docString = '' # TODO: get doc string for class? initer = Initializer(TokenFix.empty, TokenFix.empty, this, params, isNames, attribs, docString) overload as MemberOverload? = nil other = .declForName('cue.init') if other if other inherits MemberOverload overload = other else if other inherits AbstractMethod overload = MemberOverload(other) .registerOverload(overload to !) else throw FallThroughException([this, initer, other]) if overload overload.addMember(initer) else .addDecl(initer) def _scanClrProperties for propInfo in _clrType.getProperties(BindingFlags(Instance, Static, DeclaredOnly, Public, NonPublic)) getMethod = propInfo.getGetMethod(true) # true means include nonpublic setMethod = propInfo.getSetMethod(true) visible = false if getMethod if getMethod.isPublic or getMethod.isFamily theMethod = getMethod visible = true if not visible and setMethod if setMethod.isPublic or setMethod.isFamily theMethod = setMethod to ! visible = true if not visible, continue if theMethod.declaringType is not _clrType, continue if propInfo.name == _defaultMemberName and propInfo.getIndexParameters and propInfo.getIndexParameters.length _scanClrIndexer(propInfo) continue if _badClrRelatedType(propInfo.propertyType) continue attribs = AttributeList() # TODO: docString = '' # TODO: get doc string # TODO: eventually the isNames need to be an the property part level (get or set) rather than the property level, like in C# and the CLR runtime isNames = _isNamesForMethodInfo(theMethod) prop = Property(TokenFix.empty, TokenFix.empty, this, Utils.cobraNameForNativeMemberName(propInfo.name), _clrMemberTypeResultProxy(propInfo, propInfo.propertyType), isNames, attribs, docString) prop.binaryName = propInfo.name if propInfo.canRead prop.makeGetPart(TokenFix.empty) if propInfo.canWrite prop.makeSetPart(TokenFix.empty) other = .declForName(prop.name) if other # happens from C# classes that name their properties and vars Foo and foo .removeDecl(other) .addDecl(prop) def _scanClrIndexer(propInfo as PropertyInfo) for paramInfo in propInfo.getIndexParameters if _badClrRelatedType(paramInfo.parameterType) return params = _scanClrParams(propInfo.getIndexParameters) attribs = AttributeList() # TODO: docString = '' # TODO: get doc string for class? if propInfo.canRead isNames = _isNamesForMethodInfo(propInfo.getGetMethod(true) to !) else if propInfo.canWrite isNames = _isNamesForMethodInfo(propInfo.getSetMethod(true) to !) else throw FallThroughException(propInfo) indexer = Indexer(TokenFix.empty, TokenFix.empty, this, r'[]', params, _clrMemberTypeResultProxy(propInfo, propInfo.propertyType), isNames, attribs, docString) overload as MemberOverload? = nil other = .declForName(r'[]') if other if other inherits MemberOverload overload = other else if other inherits Indexer overload = MemberOverload(other) .registerOverload(overload to !) else throw FallThroughException([this, indexer, other]) if overload overload.addMember(indexer) else .addDecl(indexer) def _scanClrMethods for methInfo in _clrType.getMethods(BindingFlags(Instance, Static, DeclaredOnly, Public, NonPublic)) if methInfo.isSpecialName, continue if methInfo.isAssembly, continue if methInfo.isPrivate, continue if methInfo.declaringType is not _clrType, continue skip = false if _badClrRelatedType(methInfo.returnType) skip = true else for paramInfo in methInfo.getParameters if _badClrRelatedType(paramInfo.parameterType) skip = true break if skip, continue if methInfo.isGenericMethod or methInfo.isGenericMethodDefinition # print 'xxx generic methInfo, [methInfo.isGenericMethod], [methInfo.isGenericMethodDefinition], [methInfo]' if 'ConvertAll' in methInfo.toString # TODO continue if '.' in methInfo.name # TODO: these are mostly (maybe all) explicit implementations of interfaces # print 'xxx dotted name: [methInfo]' continue name = Utils.cobraNameForNativeMemberName(methInfo.name) genericParams = List() for genArg in methInfo.getGenericArguments genericParams.add(GenericParam(ClrNativeType(genArg))) params = _scanClrParams(methInfo.getParameters) isNames = _isNamesForMethodInfo(methInfo) attribs = _attribsForMethodInfo(methInfo) docString = '' # TODO: get doc string for class? implementsTypeNode as ITypeProxy? # TODO: explicit interface implementation? method = Method(TokenFix.empty, TokenFix.empty, this, name, genericParams, params, _clrMemberTypeResultProxy(methInfo, methInfo.returnType), implementsTypeNode, isNames, attribs, docString) method.binaryName = methInfo.name overload as MemberOverload? = nil other = .declForName(name) if other if other inherits MemberOverload overload = other else if other inherits AbstractMethod overload = MemberOverload(other) .registerOverload(overload to !) else throw FallThroughException([this, method, other]) if overload overload.addMember(method) else .addDecl(method) def _scanClrParams(paramInfos as ParameterInfo[]?) as List """ Returns a list of Cobra parameters given a list of CLR Reflection ParameterInfos. """ params = List() if paramInfos and paramInfos.length for paramInfo in paramInfos isVari = false isNotNull = false for attr in paramInfo.getCustomAttributes(false) if attr inherits ParamArrayAttribute isVari = true else typeName = attr.getType.name # use the type name in case "NotNull" attributes become a .NET convention if typeName.endsWith('.NotNull') or typeName == 'NotNull' isNotNull = true isRef = paramInfo.parameterType.isByRef and not paramInfo.isOut # Unpack "ByRef" types. Comes up with WinForm's .processCmdKey which has a ByRef struct argument (Message&) parameterType = if(isRef, paramInfo.parameterType.getElementType, paramInfo.parameterType) type = _clrMemberTypeProxy(parameterType, isNotNull) if isVari, type = VariTypeProxy(type) # In XNA 3.1 some types such as GraphicsDevice and Texture have a Dispose method # with one argument with an empty name (''). Cobra doesn't dig that. name = if(paramInfo.name == '', 'noname', paramInfo.name) param = Param(name, type) if isRef param.direction = Direction.InOut else if paramInfo.isIn and paramInfo.isOut and paramInfo.parameterType.isArray # an array with both InAttribute and OutAttribute # comes up in System.IO.Stream.read # http://msdn.microsoft.com/en-US/library/system.io.stream.read(v=VS.80).aspx # see test cases for "MemoryStream" "read" pass else if paramInfo.isOut param.direction = Direction.Out if paramInfo.isOptional # to-do?: and paramInfo.hasDefaultValue value = paramInfo.defaultValue param.optionalValue = SharpExpr(TokenFix.empty, if(value, .toString, 'nil')) params.add(param) return params def _scanClrEvents for eventInfo in _clrType.getEvents(BindingFlags(Instance, Static, DeclaredOnly, Public, NonPublic)) if eventInfo.getAddMethod is nil, continue if eventInfo.getAddMethod.isAssembly, continue if eventInfo.getAddMethod.isPrivate, continue if eventInfo.isSpecialName, continue if eventInfo.declaringType is not _clrType, continue if '.' in eventInfo.name # TODO: these are mostly (maybe all) explicit implementations of interfaces # print 'xxx dotted name: [methInfo]' continue name = Utils.cobraNameForNativeMemberName(eventInfo.name) if eventInfo.getAddMethod # Gtk.Object has such a beast (InternalDestroyed) isNames = _isNamesForMethodInfo(eventInfo.getAddMethod to !) else isNames = List() # TODO: # attribs = _attribsForMethodInfo(eventInfo) attribs = AttributeList() docString = '' # TODO: get doc string for class? evt = BoxEvent(TokenFix.empty, TokenFix.empty, this, name, isNames, attribs, docString, ClrTypeProxy(eventInfo.eventHandlerType)) evt.binaryName = eventInfo.name .addDecl(evt) def _isNamesForMethodInfo(mi as MethodBase) as List """ Returns the Cobra "is names" such as ['public', 'nonvirtual'] that correspond to the properties of the CLR MethodInfo. """ isNames = List(8) if mi.isAbstract, isNames.add('abstract') if mi.isAssembly, isNames.add('internal') if mi.isFamily, isNames.add('protected') if mi.isPrivate, isNames.add('private') if mi.isPublic, isNames.add('public') if mi.isStatic, isNames.add('shared') if not mi.isVirtual, isNames.add('nonvirtual') return isNames def _attribsForMethodInfo(mi as MethodBase) as AttributeList return AttributeList() # TODO: def _badClrRelatedType(t as Type?) as bool """ Returns true if the given type, which comes from a parameter or return value, is unsupported. For example, it's not public or it's nested. Members with bad types are skipped when scanning DLLs. """ if t is nil return false # Need nested types for .controls is a subclass of Control. type Control+ControlCollection if t.isNested and t.isNestedPrivate and not t.isGenericParameter # TODO: need the generic param check? return true # FYI: MS .NET 2.0 (but not Mono 1.2.6) will return true for .isNotPublic for types are "by ref" if t.isByRef # TODO: or t.isArray return _badClrRelatedType(t.getElementType) if t.isNotPublic or t.isNestedAssembly # .isNestedAssembly should imply .isNotPublic but at least in System.Windows.Forms, on Novell Mono 1.2.6, Control+LayoutType reports .isNestedAssembly but not .isNotPublic return true return false def _clrMemberTypeProxy(clrType as Type?) as ITypeProxy return _clrMemberTypeProxy(clrType, false) def _clrTypeIsVoid(clrType as Type?) as bool if clrType is nil, return true if clrType.name == 'Void' and clrType.namespace == 'System', return true return false def _clrMemberTypeProxy(clrType as Type?, notNullAttr as bool) as ITypeProxy """ Returns a type proxy for a member type such as a parameter type In CLR, reference types are nilable by default, but you can pass `true` for `notNullAttr` to indicate there was a NotNullAttribute in the DLL. """ if _clrTypeIsVoid(clrType) return .compiler.voidType else if clrType.isValueType return ClrTypeProxy(clrType) else # TODO: for generic types, should look at constraints. if constraints don't dictate nilable or not, then need to treat type special during generic construction tp = ClrTypeProxy(clrType) to ITypeProxy if not notNullAttr tp = NilableTypeProxy(tp) return tp def _clrMemberTypeResultProxy(member as MemberInfo, clrType as Type?) as ITypeProxy """ Returns a type proxy for a member result type such as a method return type, property or field. In CLR, reference types are nilable by default. """ if _clrTypeIsVoid(clrType) return .compiler.voidType else if clrType.isValueType return ClrTypeProxy(clrType) else if clrType.isValueType or clrType.isGenericParameter return ClrTypeProxy(clrType) else notNull = false for attr in member.getCustomAttributes(true) # The idea below in using the type name is to try for some compatibility with other languages that might also mark things "NotNull" # such as Eiffel, XC#, Spec#, etc. I haven't checked to see what degree of compatibility has been achieved. name = attr.getType.name if name == 'NotNull' or name == 'NotNullAttribute' notNull = true break # CC: could the above be a one-liner? # notNull = for attr in member.getCustomAttributes(true) where attr.getType.name == 'NotNull' get one true # notNull = any true for attr in member.getCustomAttributes(true) where attr.getType.name == 'NotNull' t = ClrTypeProxy(clrType) to ITypeProxy return if(notNull, t, NilableTypeProxy(t)) class Class is partial def scanNativeTypeClr base.scanNativeTypeClr _scanClrIsNames _scanClrImplements _scanClrNestedTypes _scanClrFields _scanClrInitializers _scanClrProperties _scanClrMethods _scanClrEvents # TODO: _scanClrEnums # TODO: scan all other nested types class Interface is partial def scanNativeTypeClr base.scanNativeTypeClr _scanClrIsNames _scanClrImplements _scanClrNestedTypes _scanClrFields _scanClrProperties _scanClrMethods _scanClrEvents class Struct is partial def scanNativeTypeClr base.scanNativeTypeClr _scanClrIsNames _scanClrImplements _scanClrNestedTypes _scanClrFields _scanClrInitializers _scanClrProperties _scanClrMethods _scanClrEvents class Extension is partial def scanNativeTypeClr # this only for Cobra specific extensions. Example: class Extend_String_1939 base.scanNativeTypeClr _scanClrIsNames #_scanClrImplements #_scanClrNestedTypes #_scanClrFields #_scanClrInitializers #_scanClrProperties _scanClrMethods for decl in .declsInOrder if decl inherits Method if decl.isShared, decl.removeIsName('shared') #_scanEvents def _scanClrParams(paramInfos as ParameterInfo[]?) as List is override # the first argument is implicit in an Extension results = base._scanClrParams(paramInfos) return results[1:] def clrExtnNativeType(nativeType as NativeType) as NativeType # the real extended type is the type of the first argument of any method for methInfo in _clrType.getMethods if methInfo.getParameters == nil or methInfo.getParameters.length == 0 throw SourceException('Cannot scan extension class [nativeType.fullName] due to method [methInfo]') nativeType = ClrNativeType(methInfo.getParameters[0].parameterType) break return nativeType class EnumDecl is partial cue init(parent as IParentSpace?, nativeType as NativeType) .init(parent, nativeType, List(), '') def setUnderlyingTypeClr _storageTypeNode = ClrTypeProxy(Enum.getUnderlyingType((_nativeType to ClrNativeType).backEndType)) def scanNativeTypeClr # TODO: read attribs _needScanNativeType = false clrType = (_nativeType to ClrNativeType).clrType isByte = Enum.getUnderlyingType(clrType).name == 'Byte' is64 = Enum.getUnderlyingType(clrType).name == 'Int64' isU32 = Enum.getUnderlyingType(clrType).name == 'UInt32' isU64 = Enum.getUnderlyingType(clrType).name == 'UInt64' values = Enum.getValues(clrType) i = 0 #print clrType for name in Enum.getNames(clrType) #print name value = values.getValue(i) # CC: lameness follows if isByte intValue = int.parse((value to uint8).toString) else if is64 try intValue = int.parse((value to int64).toString) catch OverflowException intValue = 999 # CC: omg. but probably doesn't affect anything. we're reading the DLL here, not creating one else if isU32 try intValue = int.parse((value to uint32).toString) catch OverflowException intValue = 2147483647 else if isU64 try intValue = int.parse((value to uint64).toString) catch OverflowException intValue = 2147483647 else intValue = value to int member = EnumMember(name, intValue) member.enumDecl = this .addDecl(member) i += 1