| 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'][0]; |
| 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 var matches = []; |
| 191 for (var function in cls.functions) { |
| 192 assert(cls.loaded); |
| 193 if (name == function.name) { |
| 194 return function; |
| 195 } |
| 196 } |
| 197 return null; |
| 198 } |
| 199 |
| 200 // TODO(turnidge): This does not handle named functions which are |
| 201 // inside of named functions, e.g. foo.bar.baz. |
| 202 static Future<SourceLocation> _parseFunction(Debugger debugger, |
| 203 Match match) { |
| 204 Isolate isolate = debugger.isolate; |
| 205 var base = match.group(1); |
| 206 var qualifier = match.group(2); |
| 207 assert(base != null); |
| 208 |
| 209 return _lookupClass(isolate, base).then((classes) { |
| 210 var functions = []; |
| 211 if (qualifier == null) { |
| 212 // Unqualified name is either a function or a constructor. |
| 213 functions.addAll(_lookupFunction(isolate, base)); |
| 214 |
| 215 for (var cls in classes) { |
| 216 // Look for a self-named constructor. |
| 217 var constructor = _getConstructor(cls, cls.name); |
| 218 if (constructor != null) { |
| 219 functions.add(constructor); |
| 220 } |
| 221 } |
| 222 } else { |
| 223 // Qualified name. |
| 224 var functionName = qualifier.substring(1); |
| 225 for (var cls in classes) { |
| 226 assert(cls.loaded); |
| 227 for (var function in cls.functions) { |
| 228 if (function.kind == FunctionKind.kConstructor) { |
| 229 // Constructor names are class-qualified. |
| 230 if (match.group(0) == function.name) { |
| 231 functions.add(function); |
| 232 } |
| 233 } else { |
| 234 if (functionName == function.name) { |
| 235 functions.add(function); |
| 236 } |
| 237 } |
| 238 } |
| 239 } |
| 240 } |
| 241 if (functions.length == 0) { |
| 242 return new SourceLocation.error( |
| 243 "Function '${match.group(0)}' not found"); |
| 244 } else if (functions.length == 1) { |
| 245 return new SourceLocation.func(functions[0]); |
| 246 } else { |
| 247 // TODO(turnidge): Allow the user to disambiguate. |
| 248 return new SourceLocation.error( |
| 249 "Function '${match.group(0)}' is ambigous"); |
| 250 } |
| 251 return new SourceLocation.error('foo'); |
| 252 }); |
| 253 } |
| 254 |
| 255 static RegExp partialSourceLocMatcher = |
| 256 new RegExp(r'^([^\d:]?[^:]+[:]?)?(\d+)?([:]\d+)?'); |
| 257 static RegExp partialFunctionMatcher = new RegExp(r'^([^.]*)([.][^.]*)?'); |
| 258 |
| 259 /// Completes a partial source location description. |
| 260 static Future<List<String>> complete(Debugger debugger, String locDesc) { |
| 261 List<Future<List<String>>> pending = []; |
| 262 var match = partialFunctionMatcher.firstMatch(locDesc); |
| 263 if (match != null) { |
| 264 pending.add(_completeFunction(debugger, match)); |
| 265 } |
| 266 |
| 267 match = partialSourceLocMatcher.firstMatch(locDesc); |
| 268 if (match != null) { |
| 269 pending.add(_completeFile(debugger, match)); |
| 270 } |
| 271 |
| 272 return Future.wait(pending).then((List<List<String>> responses) { |
| 273 var completions = []; |
| 274 for (var response in responses) { |
| 275 completions.addAll(response); |
| 276 } |
| 277 return completions; |
| 278 }); |
| 279 } |
| 280 |
| 281 static Future<List<String>> _completeFunction(Debugger debugger, |
| 282 Match match) { |
| 283 Isolate isolate = debugger.isolate; |
| 284 var base = match.group(1); |
| 285 var qualifier = match.group(2); |
| 286 base = (base == null ? '' : base); |
| 287 |
| 288 if (qualifier == null) { |
| 289 return _lookupClass(isolate, base, allowPrefix:true).then((classes) { |
| 290 var completions = []; |
| 291 |
| 292 // Complete top-level function names. |
| 293 var functions = _lookupFunction(isolate, base, allowPrefix:true); |
| 294 var funcNames = functions.map((f) => f.name).toList(); |
| 295 funcNames.sort(); |
| 296 completions.addAll(funcNames); |
| 297 |
| 298 // Complete class names. |
| 299 var classNames = classes.map((f) => f.name).toList(); |
| 300 classNames.sort(); |
| 301 completions.addAll(classNames); |
| 302 |
| 303 return completions; |
| 304 }); |
| 305 } else { |
| 306 return _lookupClass(isolate, base, allowPrefix:false).then((classes) { |
| 307 var completions = []; |
| 308 for (var cls in classes) { |
| 309 for (var function in cls.functions) { |
| 310 if (function.kind == FunctionKind.kConstructor) { |
| 311 if (function.name.startsWith(match.group(0))) { |
| 312 completions.add(function.name); |
| 313 } |
| 314 } else { |
| 315 if (function.qualifiedName.startsWith(match.group(0))) { |
| 316 completions.add(function.qualifiedName); |
| 317 } |
| 318 } |
| 319 } |
| 320 } |
| 321 completions.sort(); |
| 322 return completions; |
| 323 }); |
| 324 } |
| 325 } |
| 326 |
| 327 static Future<List<String>> _completeFile(Debugger debugger, Match match) { |
| 328 var scriptName = match.group(1); |
| 329 var lineStr = match.group(2); |
| 330 var colStr = match.group(3); |
| 331 if (lineStr != null || colStr != null) { |
| 332 // TODO(turnidge): Complete valid line and column numbers. |
| 333 return new Future.value([]); |
| 334 } |
| 335 scriptName = (scriptName == null ? '' : scriptName); |
| 336 |
| 337 return _lookupScript(debugger.isolate, scriptName, allowPrefix:true) |
| 338 .then((scripts) { |
| 339 List completions = []; |
| 340 for (var script in scripts) { |
| 341 completions.add(script.name + ':'); |
| 342 } |
| 343 completions.sort(); |
| 344 return completions; |
| 345 }); |
| 346 } |
| 347 |
| 348 String toString() { |
| 349 if (valid) { |
| 350 if (function != null) { |
| 351 return '${function.qualifiedName}'; |
| 352 } else if (col != null) { |
| 353 return '${script.name}:${line}:${col}'; |
| 354 } else { |
| 355 return '${script.name}:${line}'; |
| 356 } |
| 357 } |
| 358 return 'invalid source location (${errorMessage})'; |
| 359 } |
| 360 |
| 361 Script script; |
| 362 int line; |
| 363 int col; |
| 364 ServiceFunction function; |
| 365 String errorMessage; |
| 366 bool get valid => (errorMessage == null); |
| 367 } |
| OLD | NEW |