Changeset 1770 for cobra

Show
Ignore:
Timestamp:
11/19/08 23:02:54 (7 weeks ago)
Author:
Chuck.Esterbrook
Message:

Make the -files: option "relative" meaning that the paths inside that file are relative to that file (instead of say, the current working directory).
Code cleanup.
Start breaking CobraLang.cobra down into multiple files.

Location:
cobra/trunk/Source
Files:
5 added
3 modified

Legend:

Unmodified
Added
Removed
  • cobra/trunk/Source/CobraLang.cobra

    r1700 r1770  
    1414                inherits Attribute 
    1515                pass 
    16  
    17         class Tracer 
    18                 inherits Object 
    19                 """ 
    20                 Used to implement the trace statement. 
    21                 """ 
    22  
    23                 var _isActive = true 
    24                 var _willAutoFlush = true 
    25                 var _dest as TextWriter 
    26                 var _separator = '; ' 
    27                 var _prefix = 'trace: ' 
    28                 var _willOutputDirectoryNames = false 
    29  
    30                 def init 
    31                         """ 
    32                         Initializes the tracer with Console.out as the destination. 
    33                         """ 
    34                         .init(Console.out) 
    35  
    36                 def init(dest as TextWriter) 
    37                         base.init 
    38                         _dest = dest 
    39  
    40                 pro isActive from var 
    41                         """ 
    42                         When false, the `trace` methods will produce no output. 
    43                         """ 
    44  
    45                 pro willAutoFlush from var 
    46                         """ 
    47                         When true, `destination.flush` is invoked after every trace. 
    48                         Defaults to `true`. 
    49                         """ 
    50  
    51                 pro willOutputDirectoryNames from var 
    52                         """ 
    53                         When true, full path names with directories are output. 
    54                         Otherwise, only the base filename of a source file is output. 
    55                         Defaults to `false`. 
    56                         """ 
    57  
    58                 pro destination from _dest 
    59                         """ 
    60                         The TextWriter where all trace output is sent to. 
    61                         """ 
    62  
    63                 pro separator from var 
    64                         """ 
    65                         The separator string used between items of both name/value pairs and source information. 
    66                         Default is '; '. 
    67                         """ 
    68  
    69                 pro prefix from var 
    70                         """ 
    71                         The prefix string used at the beginning of every trace. 
    72                         Default is 'trace: '. 
    73                         """ 
    74  
    75                 def trace(source as SourceSite) 
    76                         if .isActive 
    77                                 print to _dest, '[.prefix][source.oneLiner(.separator, .willOutputDirectoryNames)]' 
    78                                 if .willAutoFlush 
    79                                         _dest.flush 
    80  
    81                 def trace(source as SourceSite, nameValues as vari Object) 
    82                         require nameValues.length % 2 == 0 
    83                         if .isActive 
    84                                 _dest.write(.prefix) 
    85                                 sep = .separator 
    86                                 i = 0 
    87                                 while i < nameValues.length-1 
    88                                         name = nameValues[i] to String 
    89                                         value = CobraImp._techStringMaker.makeString(nameValues[i+1]) 
    90                                         _dest.write('[name]=[value][sep]') 
    91                                         i += 2 
    92                                 _dest.writeLine(source.oneLiner(.separator, .willOutputDirectoryNames)) 
    93                                 if .willAutoFlush 
    94                                         _dest.flush 
    95  
    96         class Visitor 
    97                 is abstract 
    98                 """ 
    99                 The visitor design pattern is a way of separating an algorithm from an object structure upon 
    100                 which it operates. This enables the addition of new operations to existing object structures 
    101                 without modifying those structures. [1] 
    102          
    103                 This class implements the visitor pattern, but via reflection so as to not require that the 
    104                 classes being visited implement any additional methods for "double dispatch". This approach 
    105                 enables less coding and an even greater degree of modularity. It also enables visitation 
    106                 directly on final classes, structs, library classes, etc. with no wrapper classes. [2] 
    107          
    108                 In order to leverage this class, you must create a concrete subclass. 
    109          
    110                 Subclasses must override .methodName and then simply implement visitation methods with that 
    111                 name and the appropriate parameter types. 
    112          
    113                 When enumerating through subobjects, invoke .dispatch to get type specific dispatch. 
    114          
    115                 If you add a subclass to the class hierarchy that is being visited then you must either 
    116                         (1) remember to implement a method in your visitor class for that specific type, or 
    117                         (2) be content when a visitation method for one of its ancestor classes is invoked for it. 
    118          
    119                 If you look at the example much further below, you will get a better idea of what to do. 
    120                 Another read of the notes after examining the example could be useful. 
    121          
    122                 Alternatives to the visitor pattern: 
    123          
    124                         * Class extensions. You could put multiple, related class extensions in the same file. 
    125                           A pro is that this can make the additional functionality feel like a natural part of the 
    126                           classes. A con is that state must be passed from method to method. Also, method signatures 
    127                           will have to be updated if additional state is added (unless state is grouped into a 
    128                           single context/container object). With the visitor pattern, the state can be stored in the 
    129                           instance of the visitor. 
    130          
    131                         * Partial classes. These have the same advantages of class extensions, but without the 
    132                           disadvantage of having to pass state around. However, the con is that this is not 
    133                           available if the classes are external to your project. For example, if the classes come 
    134                           from a library or you are writing a plugin for an application, then partial classes are 
    135                           not available. (Also, as of 2008-07-07, Cobra has not yet implemented partial classes.) 
    136          
    137                 References: 
    138          
    139                         [1] http://en.wikipedia.org/wiki/Visitor_pattern 
    140                         [2] http://www.javaworld.com/javaworld/javatips/jw-javatip98.html 
    141          
    142                 Future: 
    143          
    144                         * Could add a 'strict' mode where if the argument type is not an exact match for the found 
    145                           method, an exception is raised. This would help when you don't desire that subclasses are 
    146                           silently handled as described above. 
    147                 """ 
    148          
    149                 var _willCache = true 
    150                 var _methods = Dictionary<of Type, MethodInfo>() 
    151                 var _methodName as String 
    152                  
    153                 def init 
    154                         _methodName = .methodName 
    155                         if _methodName[0].isLower 
    156                                 _methodName = _methodName[0].toUpper.toString + _methodName[1:] 
    157          
    158                 get methodName as String is abstract 
    159                         """ 
    160                         Returns the method name looked up in .dispatch. 
    161                         """ 
    162                         # ensure result.length > 0 
    163          
    164                 def dispatch(obj as Object?) 
    165                         """ 
    166                         Performs type specific dispatch on the given object. 
    167                         A subclass can override this method to change the dispatch logic. 
    168                         """ 
    169                         if obj is nil, return 
    170                         objType = obj.getType 
    171                         methInfo as MethodInfo? 
    172                         if _willCache, _methods.tryGetValue(objType, out methInfo) 
    173                         if methInfo is nil 
    174                                 methInfo = .getType.getMethod(_methodName, @[objType]) 
    175                                 if methInfo is nil 
    176                                         throw InvalidOperationException('Cannot find a method "[_methodName]" with arg type "[objType]"') 
    177                                 _methods[objType] = methInfo to ! 
    178                         methInfo.invoke(this, @[obj])  # would be nice to speed this up 
    179          
    180                 def dispatch(objects as System.Collections.IEnumerable) 
    181                         """ 
    182                         Performs type specific dispatch on each object in order. 
    183                         """ 
    184                         for obj in objects, .dispatch(obj) 
    185  
    18616 
    18717        class CobraCore 
     
    834664 
    835665 
    836         class HtmlExceptionReportWriter 
    837                 inherits ExceptionReportWriter 
    838  
    839                 var _tw as TextWriter? 
    840                 var _dumpObjectCount as int 
    841                 var _maxDumpObjectCount = 250 
    842          
    843                 def init 
    844                         .maxDumpObjectCount = CobraCore.maxDumpObjectCount 
    845  
    846                 get tw from var 
    847  
    848                 pro maxDumpObjectCount from var 
    849                         """ 
    850                         Controls the maximum number of objects dumped  in the exception report. 
    851                         Defaults to 250 which can easily result in a 5MB exception report. 
    852                         """ 
    853  
    854                 def writeReport(tw as TextWriter, exc as Exception?) 
    855                         base.writeReport(tw, exc) 
    856                         Console.out.writeLine 
    857  
    858                 def writeReport(tw as TextWriter, exc as Exception?, frames as Stack<of CobraFrame>?) is override 
    859                         # dump the most recent stack frames first since the HTML file will be displayed at the top in the browser 
    860                  
    861                         _tw = tw 
    862                         _dumpObjectCount = 0 
    863                  
    864                         tw.writeLine('<html>') 
    865                         tw.writeLine('<head>') 
    866                         exePath = CobraCore.findCobraExe 
    867                         if exePath 
    868                                 path = Path.combine(Path.getDirectoryName(exePath), 'styles-exception-report.css') 
    869                                 path = 'file://' + path.replace(Path.directorySeparatorChar, c'/') 
    870                                 tw.writeLine('<link href="[path]" rel=stylesheet type="text/css">') 
    871                         tw.writeLine('<link href=styles-exception-report.css rel=stylesheet type="text/css">') 
    872                         tw.writeLine('<meta http-equiv="content-type" content="text/html; charset=utf-8">') 
    873                         tw.writeLine('</head>') 
    874                         tw.writeLine('<body>') 
    875                         tw.writeLine('<div class=sstHeading>Cobra Exception Report</div>') 
    876                  
    877                         tw.writeLine('<div class=topLinks>') 
    878                         tw.writeLine('<a href=http://Cobra-Language.com/>Cobra</a> &nbsp;') 
    879                         tw.writeLine('<a href=http://Cobra-Language.com/downloads/>Downloads</a> &nbsp;') 
    880                         tw.writeLine('<a href=http://Cobra-Language.com/docs/>Docs</a> &nbsp;') 
    881                         tw.writeLine('<a href=http://Cobra-Language.com/how-to/>How To</a> &nbsp;') 
    882                         tw.writeLine('<a href=http://Cobra-Language.com/samples/>Samples</a> &nbsp;') 
    883                         tw.writeLine('<a href=http://CobraLang.BlogSpot.com/>Blog</a> &nbsp;') 
    884                         tw.writeLine('<a href=http://Cobra-Language.com/forums/>Discussion</a> &nbsp;') 
    885                         tw.writeLine('<a href=http://Cobra-Language.com/source/>Source</a> &nbsp;') 
    886                         tw.writeLine('<a href=http://Cobra-Language.com/docs/contact/>Contact</a>') 
    887                         tw.writeLine('</div>') 
    888  
    889                         tw.writeLine('<div class=section>') 
    890                         tw.writeLine('<div class=title>Header</div>') 
    891                         tw.writeLine('<table class=keyValues border=0 cellpadding=1 cellspacing=1>') 
    892                         if CobraCore.isRunningOnMono 
    893                                 # Mono 1.9 on Mac OS X 10.4 chokes so hard on ProcessName that it won't even throw an exception 
    894                                 name = '' 
    895                         else 
    896                                 name = System.Diagnostics.Process.getCurrentProcess.processName 
    897                         if name.endsWith('mono') 
    898                                 for part in Environment.commandLine.split(c' ') 
    899                                         if part.endsWith('.exe') 
    900                                                 name = Path.getFileName(part) to !  # CC: method should have: ensure arg and arg.length implies result; um, that'll be awhile to both put in and interpret at compile time! 
    901                                                 break 
    902                         if name <> '' 
    903                                 _headerPair('Program', name) 
    904                         _headerPair('When', DateTime.now) 
    905                         _headerPair('Command Line', Environment.commandLine) 
    906                         _headerPair('Current Directory', Environment.currentDirectory) 
    907                         _headerPair('Machine Name', Environment.machineName) 
    908                         _headerPair('Cobra', CobraCore.version) 
    909                         _headerPair('CLR', Environment.version) 
    910                         if Environment.workingSet to decimal  # CC: axe cast. workingSet is really C# 'long' or Cobra 'int64' 
    911                                 _headerPair('Working Set', Environment.workingSet) 
    912                         tw.writeLine('</table>') 
    913                         tw.writeLine('</div> <!-- section -->') 
    914                  
    915                         objects = ObjectCatalog() 
    916                  
    917                         if exc 
    918                                 tw.writeLine('<div class=section>') 
    919                                 tw.writeLine('<div class=title>Exception</div>') 
    920                                 objects.record(exc) 
    921                                 .dumpObjectAsHtml(objects.serialNumFor(exc), exc, objects) 
    922                  
    923                         startingSerialNum = objects.minSerialNum 
    924                         tw.writeLine('<div class=section>') 
    925                         tw.writeLine('<div class=title>Stack Frames</div>') 
    926                         if frames is nil 
    927                                 tw.writeLine('<p>There is no detailed stack trace. You can turn this on with "cobra -dst ..." and see significantly more information about the state of the program including the details of every argument and local variable. There is a performance cost, but the slowdown is likely worth it if you are unable to diagnose this problem.</p>') 
    928                         else 
    929                                 if not frames.count 
    930                                         tw.writeLine('<p>No stack frames.</p>') 
    931                                 else 
    932                                         frameList = List<of CobraFrame>(frames) 
    933                                         frameList.reverse 
    934                                         tw.writeLine('<table class=stack border=0 cellpadding=2 cellspacing=0>') 
    935                                         i = 0 
    936                                         for frame in frameList 
    937                                                 frame.dumpHtml(tw, i, objects, ref .willDumpHtmlFor) 
    938                                                 i += 1 
    939                                         tw.writeLine('</table>') 
    940                         tw.writeLine('</div> <!-- section -->') 
    941  
    942                         tw.writeLine('<div class=section>') 
    943                         tw.writeLine('<div class=title>Objects</div>') 
    944                         serialNum = if(startingSerialNum > 0, startingSerialNum+1, objects.minSerialNum) 
    945                         while objects.contains(serialNum) and _dumpObjectCount <= _maxDumpObjectCount 
    946                                 .dumpObjectAsHtml(serialNum, objects.objectFor(serialNum), objects) 
    947                                 serialNum += 1 
    948                         tw.writeLine('</div> <!-- section -->') 
    949  
    950                         tw.writeLine('</body>') 
    951                         tw.writeLine('</html>') 
    952  
    953                 def _headerPair(key as String, value as Object?) 
    954                         key = .htmlEncode(key) 
    955                         value ?= '' 
    956                         value = .htmlEncode(value.toString) 
    957                         .tw.writeLine('<tr class=keyValue> <td class=key> [key] </td> <td> &nbsp;=&nbsp; </td> <td class=value> [value] </td> </tr>') 
    958  
    959                 def htmlEncode(obj as Object) as String 
    960                         return CobraCore.htmlEncode(obj) 
    961  
    962                 def htmlEncode(s as String) as String 
    963                         return CobraCore.htmlEncode(s) 
    964  
    965                 def htmlFormat(s as String) as Html 
    966                         s = .htmlEncode(s) 
    967                         # re = Regex(r'\n[ ]+', ...  # CC: ack, no delegates so can't get length 
    968                         sb = StringBuilder(s.length*2) 
    969                         state = 0 
    970                         for c in s 
    971                                 branch state 
    972                                         on 0 
    973                                                 sb.append(c) 
    974                                                 if c == c'\n' 
    975                                                         state = 1 
    976                                         on 1 
    977                                                 if c == c' ' 
    978                                                         sb.append('&nbsp;') 
    979                                                 else if c == c'\n' 
    980                                                         sb.append(c) 
    981                                                 else 
    982                                                         sb.append(c) 
    983                                                         state = 0 
    984                         s = sb.toString 
    985                         s = s.replace('\r', '').replace('\n', '<br>') 
    986                         return Html(s) 
    987                  
    988                 def willDumpHtmlFor(obj as Object?) as bool                      
    989                         if obj is nil 
    990                                 return false 
    991                         if obj inherits int 
    992                                 return false 
    993                         # TODO: int64 and other int sizes 
    994                         if obj inherits String, return false 
    995                         if obj inherits decimal or obj inherits float, return false 
    996                         if obj inherits bool or obj inherits char, return false 
    997                         if obj inherits Html, return false 
    998                         if obj inherits CobraDirectString, return false 
    999                         if obj.getType.isEnum, return false 
    1000                         fullName = obj.getType.fullName 
    1001                         if fullName == 'System.Security.Policy.Evidence'  # 2007-07-11 CE: causes problems, at least on mono 1.2.4 
    1002                                 return false 
    1003                         if fullName == 'System.IntPtr'  # not interesting. # TODO: skip any type that has zero properties and does not implement custom exception reporting 
    1004                                 return false 
    1005                         return true 
    1006  
    1007                 def willDumpHtmlForConservative(obj as Object?) as bool 
    1008                         if not .willDumpHtmlFor(obj), return false 
    1009                         if obj.getType.isNested, return false 
    1010                         if 'IEquatableOf' in obj.getType.name, return false 
    1011                         return true 
    1012                  
    1013                 def dumpObjectAsHtml(serialNum as int, obj as Object, objects as ObjectCatalog?) 
    1014                         tw = .tw 
    1015                         if _dumpObjectCount % 10 == 0  # TODO: change 10 to a public property. value < 1 means not to write progress 
    1016                                 Console.out.write('.') 
    1017                                 Console.out.flush 
    1018                         _dumpObjectCount += 1 
    1019                         if objects 
    1020                                 objects.record(obj) 
    1021                         tw.writeLine('<a name=Object[serialNum]></a>') 
    1022                         tw.writeLine('<table class=object border=0 cellpadding=0 cellspacing=0>') 
    1023                         tw.writeLine('<tr class=objectTitle> <td class=objectTitle colspan=2> [.htmlEncode(CobraCore.typeName(obj.getType))] &nbsp; <font size=-1>([serialNum])</font> </td> </tr>') 
    1024                         try 
    1025                                 value = obj.toString to Object? 
    1026                         catch exc as Exception 
    1027                                 value = 'Caught exception while reading value: [exc.toString]' 
    1028                         row = 1 
    1029                         if obj inherits IHasSourceSite 
    1030                                 .writeObjectPair1(row, 'at', obj.sourceSite.htmlForAt) 
    1031                                 row += 1 
    1032                         .writeObjectPair1(row, 'toString', .htmlFormat(value.toString)) 
    1033                         propInfos = List<of PropertyInfo>(obj.getType.getProperties) 
    1034                         propInfos.sort(ref .comparePropertyInfoNames)  # CC: propInfos.sort(def(a as PropertyInfo, b as PropertyInfo)=a.name.compareTo(b.name)) 
    1035                         for propInfo in propInfos 
    1036                                 if propInfo.canRead and not propInfo.getGetMethod.isStatic and propInfo.name not in ['Clone', 'Copy', 'Item'] 
    1037                                         if propInfo.name == 'MetadataToken'  # problems on mono. http://bugzilla.ximian.com/show_bug.cgi?id=82161 
    1038                                                 value = '(SKIPPED)' 
    1039                                         else 
    1040                                                 try 
    1041                                                         value = propInfo.getValue(obj, nil) 
    1042                                                 catch exc as Exception 
    1043                                                         value = .htmlFormat('Caught exception while reading value: [exc.toString]') 
    1044                                                 success 
    1045                                                         value = .htmlForValue(value, objects) 
    1046                                         .writeObjectPair1(row%2+1, propInfo.name, value) 
    1047                                         row += 1 
    1048                         extendMethod = obj.getType.getMethod('ExtendObjectTable') 
    1049                         if extendMethod 
    1050                                 view = PrivateObjectViewForExceptionReport(this, row, objects) 
    1051                                 extendMethod.invoke(obj, @[view]) 
    1052                                 i = view.rowNum 
    1053                         # TODO: ack! cannot do this until qualified type problems are fixed up 
    1054                         #if obj inherits System.Collections.IList 
    1055                         if obj.getType.name.startsWith('List`') 
    1056                                 dobj = obj to dynamic 
    1057                                 count = dobj.count to int 
    1058                                 for j = 0 .. count 
    1059                                         try 
    1060                                                 value = dobj[j] 
    1061                                         catch exc as Exception 
    1062                                                 value = .htmlFormat('Caught exception while reading indexed value [j]: [exc.toString]') 
    1063                                         success 
    1064                                                 value = .htmlForValue(value, objects) 
    1065                                         .writeObjectPair1(i%2+1, r'[' + '[j]]', value) 
    1066                                         i += 1 
    1067                         else if obj.getType.name.startsWith('Dictionary')  # TODO: use if implements/inherits 
    1068                                 dobj = obj to dynamic 
    1069                                 for key in dobj.keys 
    1070                                         htmlKey as dynamic? 
    1071                                         try 
    1072                                                 htmlKey = CobraCore.toTechString(key) 
    1073                                         catch exc as Exception 
    1074                                                 htmlKey = .htmlFormat('Caught exception for toTechString(key): [exc.toString]') 
    1075                                         success 
    1076                                                 htmlKey = .htmlEncode(htmlKey) 
    1077                                         try 
    1078                                                 value = dobj[key] 
    1079                                         catch exc as Exception 
    1080                                                 value = .htmlEncode('Caught exception while reading keyed value [htmlKey]: [exc.toString]') 
    1081                                         success 
    1082                                                 value = .htmlForValue(value, objects) 
    1083                                         .writeObjectPair1(i%2+1, r'[' + htmlKey + ']', value) 
    1084                                         i += 1 
    1085  
    1086                         tw.writeLine('</table>') 
    1087  
    1088                 def comparePropertyInfoNames(a as PropertyInfo, b as PropertyInfo) as int 
    1089                         return a.name.compareTo(b.name) 
    1090  
    1091                 def htmlForValue(value as dynamic?, objects as ObjectCatalog?) as dynamic 
    1092                         """ 
    1093                         Returns the .toTechString of the value, encoded for HTML. 
    1094                         Gracefully handles exceptions and also creates links to objects. 
    1095                         """ 
    1096                         if value inherits Html  # this feature is used by AssertException, but it's interesting that this would then make Html() objects less recognizable when they show up in the report as a normal part of the program that failed 
    1097                                 return value 
    1098                         s as dynamic? 
    1099                         try 
    1100                                 try 
    1101                                         s = CobraCore.toTechString(value) 
    1102                                 catch exc as Exception 
    1103                                         s = .htmlFormat('Caught exception while converting value toTechString: [exc.toString]') 
    1104                                 success 
    1105                                         s = .htmlFormat(s to !) 
    1106                                         if value and objects 
    1107                                                 willLink = false 
    1108                                                 if objects.contains(value to passthrough) 
    1109                                                         willLink = true 
    1110                                                 else if .willDumpHtmlForConservative(value) and objects 
    1111                                                         objects.record(value to passthrough) 
    1112                                                         willLink = true 
    1113                                                 if willLink 
    1114                                                         s = '<a class=objectDetails href=#Object[objects.serialNumFor(value to passthrough)]>[s]</a>' 
    1115                         catch topExc as SystemException  # for example, System.Security.Policy.Evidence as a dictionary key causes problems (at least on mono 1.2.4) 
    1116                                 try 
    1117                                         s = value.toString 
    1118                                 catch toStringExc as Exception 
    1119                                         s = '(htmlForValue: Exception during toString: [toStringExc];[Environment.newLine]Exception during processing: [topExc])' 
    1120                                 success 
    1121                                         s = '(htmlForValue: value=[s]; Exception during processing: [topExc])' 
    1122                                 try 
    1123                                         s = .htmlFormat(s to !) 
    1124                                 catch Exception 
    1125                                         pass  # forget it 
    1126                         return s to ! 
    1127          
    1128                 def writeObjectPair1(i as int, key as String, valueHtml as Object?) is internal 
    1129                         require 
    1130                                 i == 1 or i == 2 
    1131                                 key.length 
    1132                         body 
    1133                                 key = key[0].toString.toLower + key[1:] 
    1134                                 key = .htmlEncode(key) 
    1135                                 .writeObjectPair2(i, key, valueHtml) 
    1136  
    1137                 def writeObjectPair2(i as int, preFormattedKey as String, valueHtml as Object?) is internal 
    1138                         require 
    1139                                 i == 1 or i == 2 
    1140                                 preFormattedKey.length 
    1141                         body 
    1142                                 .tw.writeLine('<tr class=keyValue[i]> <td class=key[i]> [preFormattedKey] </td> <td class=value[i]> [valueHtml ? ''] </td> </tr>') 
    1143  
    1144         interface IObjectView 
    1145                 """ 
    1146                 TODO: document this 
    1147                 """ 
    1148                  
    1149                 def addEntry(key as String, value as Object?) 
    1150  
    1151         class PrivateObjectViewForExceptionReport 
    1152                 is internal 
    1153                 implements IObjectView 
    1154                  
    1155                 var _reportWriter as HtmlExceptionReportWriter 
    1156                 var _rowNum as int 
    1157                 var _objects as ObjectCatalog? 
    1158                  
    1159                 def init(reportWriter as HtmlExceptionReportWriter, rowNum as int, objects as ObjectCatalog?) 
    1160                         _reportWriter = reportWriter 
    1161                         _rowNum = rowNum 
    1162                         _objects = objects 
    1163                  
    1164                 get rowNum from var 
    1165                  
    1166                 def addEntry(key as String, value as Object?) 
    1167                         key = _reportWriter.htmlEncode(key).replace('  ', '&nbsp;&nbsp;')  # the double space replace here works well with the indentation scheme used in AssertException 
    1168                         html = _reportWriter.htmlForValue(value, _objects) 
    1169                         _reportWriter.writeObjectPair2(_rowNum%2+1, key, html) 
    1170                         _rowNum += 1 
    1171          
    1172         sig WillDumpHtmlForMethod(value as dynamic?) as bool 
    1173  
    1174666        class CobraFrame 
    1175667