| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of debugger; | |
| 6 | |
| 7 class SourceLocation { | |
| 8 SourceLocation.file(this.script, this.line, this.col); | |
| 9 SourceLocation.func(this.function); | |
| 10 SourceLocation.error(this.errorMessage); | |
| 11 | |
| 12 static RegExp sourceLocMatcher = new RegExp(r'^([^\d:][^:]+:)?(\d+)(:\d+)?'); | |
| 13 static RegExp functionMatcher = new RegExp(r'^([^.]+)([.][^.]+)?'); | |
| 14 | |
| 15 /// Parses a source location description. | |
| 16 /// | |
| 17 /// Formats: | |
| 18 /// '' - current position | |
| 19 /// 13 - line 13, current script | |
| 20 /// 13:20 - line 13, col 20, current script | |
| 21 /// script.dart:13 - line 13, script.dart | |
| 22 /// script.dart:13:20 - line 13, col 20, script.dart | |
| 23 /// main - function | |
| 24 /// FormatException - constructor | |
| 25 /// _SHA1._updateHash - method | |
| 26 static Future<SourceLocation> parse(Debugger debugger, String locDesc) { | |
| 27 if (locDesc == '') { | |
| 28 // Special case: '' means return current location. | |
| 29 return _currentLocation(debugger); | |
| 30 } | |
| 31 | |
| 32 // Parse the location description. | |
| 33 var match = sourceLocMatcher.firstMatch(locDesc); | |
| 34 if (match != null) { | |
| 35 return _parseScriptLine(debugger, match); | |
| 36 } | |
| 37 match = functionMatcher.firstMatch(locDesc); | |
| 38 if (match != null) { | |
| 39 return _parseFunction(debugger, match); | |
| 40 } | |
| 41 return new Future.value(new SourceLocation.error( | |
| 42 "Invalid source location '${locDesc}'")); | |
| 43 } | |
| 44 | |
| 45 static Future<SourceLocation> _currentLocation(Debugger debugger) { | |
| 46 ServiceMap stack = debugger.stack; | |
| 47 if (stack == null || stack['frames'].length == 0) { | |
| 48 return new Future.value(new SourceLocation.error( | |
| 49 'A script must be provided when the stack is empty')); | |
| 50 } | |
| 51 var frame = stack['frames'][debugger.currentFrame]; | |
| 52 Script script = frame['script']; | |
| 53 return script.load().then((_) { | |
| 54 var line = script.tokenToLine(frame['tokenPos']); | |
| 55 // TODO(turnidge): Pass in the column here once the protocol supports it. | |
| 56 return new Future.value(new SourceLocation.file(script, line, null)); | |
| 57 }); | |
| 58 } | |
| 59 | |
| 60 static Future<SourceLocation> _parseScriptLine(Debugger debugger, | |
| 61 Match match) { | |
| 62 var scriptName = match.group(1); | |
| 63 if (scriptName != null) { | |
| 64 scriptName = scriptName.substring(0, scriptName.length - 1); | |
| 65 } | |
| 66 var lineStr = match.group(2); | |
| 67 assert(lineStr != null); | |
| 68 var colStr = match.group(3); | |
| 69 if (colStr != null) { | |
| 70 colStr = colStr.substring(1); | |
| 71 } | |
| 72 var line = int.parse(lineStr, onError:(_) => -1); | |
| 73 var col = (colStr != null | |
| 74 ? int.parse(colStr, onError:(_) => -1) | |
| 75 : null); | |
| 76 if (line == -1) { | |
| 77 return new Future.value(new SourceLocation.error( | |
| 78 "Line '${lineStr}' must be an integer")); | |
| 79 } | |
| 80 if (col == -1) { | |
| 81 return new Future.value(new SourceLocation.error( | |
| 82 "Column '${colStr}' must be an integer")); | |
| 83 } | |
| 84 | |
| 85 if (scriptName != null) { | |
| 86 // Resolve the script. | |
| 87 return _lookupScript(debugger.isolate, scriptName).then((scripts) { | |
| 88 if (scripts.length == 0) { | |
| 89 return new SourceLocation.error("Script '${scriptName}' not found"); | |
| 90 } else if (scripts.length == 1) { | |
| 91 return new SourceLocation.file(scripts[0], line, col); | |
| 92 } else { | |
| 93 // TODO(turnidge): Allow the user to disambiguate. | |
| 94 return new SourceLocation.error("Script '${scriptName}' is ambigous"); | |
| 95 } | |
| 96 }); | |
| 97 } else { | |
| 98 // No script provided. Default to top of stack for now. | |
| 99 ServiceMap stack = debugger.stack; | |
| 100 if (stack == null || stack['frames'].length == 0) { | |
| 101 return new Future.value(new SourceLocation.error( | |
| 102 'A script must be provided when the stack is empty')); | |
| 103 } | |
| 104 Script script = stack['frames'][0]['script']; | |
| 105 return new Future.value(new SourceLocation.file(script, line, col)); | |
| 106 } | |
| 107 } | |
| 108 | |
| 109 static Future<List<Script>> _lookupScript(Isolate isolate, | |
| 110 String name, | |
| 111 {bool allowPrefix: false}) { | |
| 112 var pending = []; | |
| 113 for (var lib in isolate.libraries) { | |
| 114 if (!lib.loaded) { | |
| 115 pending.add(lib.load()); | |
| 116 } | |
| 117 } | |
| 118 return Future.wait(pending).then((_) { | |
| 119 List matches = []; | |
| 120 for (var lib in isolate.libraries) { | |
| 121 for (var script in lib.scripts) { | |
| 122 if (allowPrefix) { | |
| 123 if (script.name.startsWith(name)) { | |
| 124 matches.add(script); | |
| 125 } | |
| 126 } else { | |
| 127 if (name == script.name) { | |
| 128 matches.add(script); | |
| 129 } | |
| 130 } | |
| 131 } | |
| 132 } | |
| 133 return matches; | |
| 134 }); | |
| 135 } | |
| 136 | |
| 137 static List<ServiceFunction> _lookupFunction(Isolate isolate, | |
| 138 String name, | |
| 139 { bool allowPrefix: false }) { | |
| 140 var matches = []; | |
| 141 for (var lib in isolate.libraries) { | |
| 142 assert(lib.loaded); | |
| 143 for (var function in lib.functions) { | |
| 144 if (allowPrefix) { | |
| 145 if (function.name.startsWith(name)) { | |
| 146 matches.add(function); | |
| 147 } | |
| 148 } else { | |
| 149 if (name == function.name) { | |
| 150 matches.add(function); | |
| 151 } | |
| 152 } | |
| 153 } | |
| 154 } | |
| 155 return matches; | |
| 156 } | |
| 157 | |
| 158 static Future<List<Class>> _lookupClass(Isolate isolate, | |
| 159 String name, | |
| 160 { bool allowPrefix: false }) { | |
| 161 var pending = []; | |
| 162 for (var lib in isolate.libraries) { | |
| 163 assert(lib.loaded); | |
| 164 for (var cls in lib.classes) { | |
| 165 if (!cls.loaded) { | |
| 166 pending.add(cls.load()); | |
| 167 } | |
| 168 } | |
| 169 } | |
| 170 return Future.wait(pending).then((_) { | |
| 171 var matches = []; | |
| 172 for (var lib in isolate.libraries) { | |
| 173 for (var cls in lib.classes) { | |
| 174 if (allowPrefix) { | |
| 175 if (cls.name.startsWith(name)) { | |
| 176 matches.add(cls); | |
| 177 } | |
| 178 } else { | |
| 179 if (name == cls.name) { | |
| 180 matches.add(cls); | |
| 181 } | |
| 182 } | |
| 183 } | |
| 184 } | |
| 185 return matches; | |
| 186 }); | |
| 187 } | |
| 188 | |
| 189 static ServiceFunction _getConstructor(Class cls, String name) { | |
| 190 for (var function in cls.functions) { | |
| 191 assert(cls.loaded); | |
| 192 if (name == function.name) { | |
| 193 return function; | |
| 194 } | |
| 195 } | |
| 196 return null; | |
| 197 } | |
| 198 | |
| 199 // TODO(turnidge): This does not handle named functions which are | |
| 200 // inside of named functions, e.g. foo.bar.baz. | |
| 201 static Future<SourceLocation> _parseFunction(Debugger debugger, | |
| 202 Match match) { | |
| 203 Isolate isolate = debugger.isolate; | |
| 204 var base = match.group(1); | |
| 205 var qualifier = match.group(2); | |
| 206 assert(base != null); | |
| 207 | |
| 208 return _lookupClass(isolate, base).then((classes) { | |
| 209 var functions = []; | |
| 210 if (qualifier == null) { | |
| 211 // Unqualified name is either a function or a constructor. | |
| 212 functions.addAll(_lookupFunction(isolate, base)); | |
| 213 | |
| 214 for (var cls in classes) { | |
| 215 // Look for a self-named constructor. | |
| 216 var constructor = _getConstructor(cls, cls.name); | |
| 217 if (constructor != null) { | |
| 218 functions.add(constructor); | |
| 219 } | |
| 220 } | |
| 221 } else { | |
| 222 // Qualified name. | |
| 223 var functionName = qualifier.substring(1); | |
| 224 for (var cls in classes) { | |
| 225 assert(cls.loaded); | |
| 226 for (var function in cls.functions) { | |
| 227 if (function.kind == FunctionKind.kConstructor) { | |
| 228 // Constructor names are class-qualified. | |
| 229 if (match.group(0) == function.name) { | |
| 230 functions.add(function); | |
| 231 } | |
| 232 } else { | |
| 233 if (functionName == function.name) { | |
| 234 functions.add(function); | |
| 235 } | |
| 236 } | |
| 237 } | |
| 238 } | |
| 239 } | |
| 240 if (functions.length == 0) { | |
| 241 return new SourceLocation.error( | |
| 242 "Function '${match.group(0)}' not found"); | |
| 243 } else if (functions.length == 1) { | |
| 244 return new SourceLocation.func(functions[0]); | |
| 245 } else { | |
| 246 // TODO(turnidge): Allow the user to disambiguate. | |
| 247 return new SourceLocation.error( | |
| 248 "Function '${match.group(0)}' is ambigous"); | |
| 249 } | |
| 250 return new SourceLocation.error('foo'); | |
| 251 }); | |
| 252 } | |
| 253 | |
| 254 static RegExp partialSourceLocMatcher = | |
| 255 new RegExp(r'^([^\d:]?[^:]+[:]?)?(\d+)?([:]\d+)?'); | |
| 256 static RegExp partialFunctionMatcher = new RegExp(r'^([^.]*)([.][^.]*)?'); | |
| 257 | |
| 258 /// Completes a partial source location description. | |
| 259 static Future<List<String>> complete(Debugger debugger, String locDesc) { | |
| 260 List<Future<List<String>>> pending = []; | |
| 261 var match = partialFunctionMatcher.firstMatch(locDesc); | |
| 262 if (match != null) { | |
| 263 pending.add(_completeFunction(debugger, match)); | |
| 264 } | |
| 265 | |
| 266 match = partialSourceLocMatcher.firstMatch(locDesc); | |
| 267 if (match != null) { | |
| 268 pending.add(_completeFile(debugger, match)); | |
| 269 } | |
| 270 | |
| 271 return Future.wait(pending).then((List<List<String>> responses) { | |
| 272 var completions = []; | |
| 273 for (var response in responses) { | |
| 274 completions.addAll(response); | |
| 275 } | |
| 276 return completions; | |
| 277 }); | |
| 278 } | |
| 279 | |
| 280 static Future<List<String>> _completeFunction(Debugger debugger, | |
| 281 Match match) { | |
| 282 Isolate isolate = debugger.isolate; | |
| 283 var base = match.group(1); | |
| 284 var qualifier = match.group(2); | |
| 285 base = (base == null ? '' : base); | |
| 286 | |
| 287 if (qualifier == null) { | |
| 288 return _lookupClass(isolate, base, allowPrefix:true).then((classes) { | |
| 289 var completions = []; | |
| 290 | |
| 291 // Complete top-level function names. | |
| 292 var functions = _lookupFunction(isolate, base, allowPrefix:true); | |
| 293 var funcNames = functions.map((f) => f.name).toList(); | |
| 294 funcNames.sort(); | |
| 295 completions.addAll(funcNames); | |
| 296 | |
| 297 // Complete class names. | |
| 298 var classNames = classes.map((f) => f.name).toList(); | |
| 299 classNames.sort(); | |
| 300 completions.addAll(classNames); | |
| 301 | |
| 302 return completions; | |
| 303 }); | |
| 304 } else { | |
| 305 return _lookupClass(isolate, base, allowPrefix:false).then((classes) { | |
| 306 var completions = []; | |
| 307 for (var cls in classes) { | |
| 308 for (var function in cls.functions) { | |
| 309 if (function.kind == FunctionKind.kConstructor) { | |
| 310 if (function.name.startsWith(match.group(0))) { | |
| 311 completions.add(function.name); | |
| 312 } | |
| 313 } else { | |
| 314 if (function.qualifiedName.startsWith(match.group(0))) { | |
| 315 completions.add(function.qualifiedName); | |
| 316 } | |
| 317 } | |
| 318 } | |
| 319 } | |
| 320 completions.sort(); | |
| 321 return completions; | |
| 322 }); | |
| 323 } | |
| 324 } | |
| 325 | |
| 326 static Future<List<String>> _completeFile(Debugger debugger, Match match) { | |
| 327 var scriptName = match.group(1); | |
| 328 var lineStr = match.group(2); | |
| 329 var colStr = match.group(3); | |
| 330 if (lineStr != null || colStr != null) { | |
| 331 // TODO(turnidge): Complete valid line and column numbers. | |
| 332 return new Future.value([]); | |
| 333 } | |
| 334 scriptName = (scriptName == null ? '' : scriptName); | |
| 335 | |
| 336 return _lookupScript(debugger.isolate, scriptName, allowPrefix:true) | |
| 337 .then((scripts) { | |
| 338 List completions = []; | |
| 339 for (var script in scripts) { | |
| 340 completions.add(script.name + ':'); | |
| 341 } | |
| 342 completions.sort(); | |
| 343 return completions; | |
| 344 }); | |
| 345 } | |
| 346 | |
| 347 String toString() { | |
| 348 if (valid) { | |
| 349 if (function != null) { | |
| 350 return '${function.qualifiedName}'; | |
| 351 } else if (col != null) { | |
| 352 return '${script.name}:${line}:${col}'; | |
| 353 } else { | |
| 354 return '${script.name}:${line}'; | |
| 355 } | |
| 356 } | |
| 357 return 'invalid source location (${errorMessage})'; | |
| 358 } | |
| 359 | |
| 360 Script script; | |
| 361 int line; | |
| 362 int col; | |
| 363 ServiceFunction function; | |
| 364 String errorMessage; | |
| 365 bool get valid => (errorMessage == null); | |
| 366 } | |
| OLD | NEW |