OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2012, 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 /** |
| 6 * Parser for Dart code based on the dart2js parser. |
| 7 * |
| 8 * Use [DartCodeParser.parse] to parse top-level code, and the returned |
| 9 * instance to parse additonal structures such as classes. |
| 10 */ |
| 11 library dart_parser; |
| 12 |
| 13 import 'dart:uri'; |
| 14 import 'dart:utf'; |
| 15 import 'package:compiler_unsupported/implementation/dart2jslib.dart' as dart2js; |
| 16 import 'package:compiler_unsupported/implementation/elements/elements.dart'; |
| 17 import 'package:compiler_unsupported/implementation/elements/modelx.dart'; |
| 18 import 'package:compiler_unsupported/implementation/scanner/scannerlib.dart'; |
| 19 import 'package:compiler_unsupported/implementation/source_file.dart'; |
| 20 import 'package:compiler_unsupported/implementation/tree/tree.dart'; |
| 21 import 'package:compiler_unsupported/implementation/util/util.dart'; |
| 22 import 'package:compiler_unsupported/implementation/util/characters.dart'; |
| 23 import 'package:compiler_unsupported/compiler.dart' as api; |
| 24 import 'package:html5lib/dom_parsing.dart' as dom_parsing; |
| 25 import 'messages.dart' show Messages; |
| 26 import 'file_system/path.dart' as fs; |
| 27 |
| 28 // TODO(jmesserly): reconcile this with DartCodeInfo |
| 29 class DartCodeParser { |
| 30 final DiagnosticListener diagnostics; |
| 31 final CompilationUnitElement unit; |
| 32 final String code; |
| 33 bool _success; |
| 34 |
| 35 bool get success => _success; |
| 36 |
| 37 Messages get messages => diagnostics.messages; |
| 38 |
| 39 DartCodeParser._parse(this.diagnostics, this.unit, this.code) { |
| 40 _success = _parseCompilationUnit(); |
| 41 } |
| 42 |
| 43 /** |
| 44 * Performs a partial parse of Dart code and returns the parser. |
| 45 * From their you can determine if the parse was a [success] and get the |
| 46 * compilation [unit]. |
| 47 * |
| 48 * The partial parse only parses the top-level structure. Futher parsing can |
| 49 * then be performed using the parser, such as [parseClass]. |
| 50 * |
| 51 * You can provide a object to receive warning and error [messages] from the |
| 52 * parser, otherwise [this.messages] will be a new messages instance that is |
| 53 * silent (does not print). |
| 54 */ |
| 55 factory DartCodeParser.parse(String path, String code, {Messages messages}) { |
| 56 if (messages == null) messages = new Messages.silent(); |
| 57 var uri = new Uri.fromComponents(path: path); |
| 58 var script = new dart2js.Script(uri, new SourceFile(uri.toString(), code)); |
| 59 var unit = new LibraryElementX(script, uri).entryCompilationUnit; |
| 60 var fileSpanInfo = _createSourceFileInfo(code); |
| 61 var diagnostics = new DiagnosticListener(fileSpanInfo, unit, messages); |
| 62 return new DartCodeParser._parse(diagnostics, unit, code); |
| 63 } |
| 64 |
| 65 bool _parseCompilationUnit() { |
| 66 int nextFreeClassId = 0; |
| 67 var idGenerator = () => nextFreeClassId++; |
| 68 var listener = new ElementListener(diagnostics, unit, idGenerator); |
| 69 |
| 70 // Try parsing the code. Note that the parser can throw an exception to bail |
| 71 // out of the call stack. |
| 72 try { |
| 73 var tokens = new StringScanner(code).tokenize(); |
| 74 new PartialParser(listener).parseUnit(tokens); |
| 75 return true; |
| 76 } on dart2js.CompilerCancelledException catch (e) { |
| 77 return false; |
| 78 } |
| 79 } |
| 80 |
| 81 ClassNode parseClass(PartialClassElement element) { |
| 82 if (element.cachedNode != null) return element.cachedNode; |
| 83 var listener = new MemberListener(diagnostics, element); |
| 84 var parser = new ClassElementParser(listener); |
| 85 var token = parser.parseTopLevelDeclaration(element.beginToken); |
| 86 assert(identical(token, element.endToken.next)); |
| 87 var classNode = listener.popNode(); |
| 88 assert(listener.nodes.isEmpty); |
| 89 return element.cachedNode = classNode; |
| 90 } |
| 91 } |
| 92 |
| 93 |
| 94 /** |
| 95 * DiagnosticListener for dart2js. Figures out warning/error spans, and adapts |
| 96 * them to the [Messages] format used in web_ui. |
| 97 * |
| 98 * This class is madness. It has to deal with spans/messages/paths/uris from |
| 99 * 3+ compilers colliding (for those keeping score: dart2js, frog remnants |
| 100 * inside dart2js, html5lib, and web_ui ...). Also a lot of this code had to be |
| 101 * copied from the dart2js Compiler class to make DiagnosticListener actually |
| 102 * work. |
| 103 * |
| 104 * Avert your eyes! |
| 105 */ |
| 106 class DiagnosticListener implements dart2js.DiagnosticListener { |
| 107 final dom_parsing.SourceFileInfo fileInfo; |
| 108 final CompilationUnitElement currentElement; |
| 109 final Messages messages; |
| 110 |
| 111 DiagnosticListener(this.fileInfo, this.currentElement, this.messages); |
| 112 |
| 113 void cancel(String reason, {node, token, instruction, element}) { |
| 114 SourceSpan span = null; |
| 115 if (node != null) { |
| 116 span = spanFromNode(node); |
| 117 } else if (token != null) { |
| 118 span = spanFromTokens(token, token); |
| 119 } else if (instruction != null) { |
| 120 span = spanFromHInstruction(instruction); |
| 121 } else if (element != null) { |
| 122 span = spanFromElement(element); |
| 123 } else { |
| 124 throw 'No error location for error: $reason'; |
| 125 } |
| 126 reportMessageString(span, reason, api.Diagnostic.ERROR); |
| 127 throw new CompilerCancelledException(reason); |
| 128 } |
| 129 |
| 130 SourceSpan spanFromTokens(Token begin, Token end, [Uri uri]) { |
| 131 if (begin == null || end == null) { |
| 132 // TODO(ahe): We can almost always do better. Often it is only |
| 133 // end that is null. Otherwise, we probably know the current |
| 134 // URI. |
| 135 throw 'Cannot find tokens to produce error message.'; |
| 136 } |
| 137 if (uri == null && currentElement != null) { |
| 138 uri = currentElement.getCompilationUnit().script.uri; |
| 139 } |
| 140 return SourceSpan.withCharacterOffsets(begin, end, |
| 141 (beginOffset, endOffset) => new SourceSpan(uri, beginOffset, endOffset)); |
| 142 } |
| 143 |
| 144 SourceSpan spanFromNode(Node node, [Uri uri]) { |
| 145 return spanFromTokens(node.getBeginToken(), node.getEndToken(), uri); |
| 146 } |
| 147 |
| 148 SourceSpan spanFromElement(Element element) { |
| 149 if (Elements.isErroneousElement(element)) { |
| 150 element = element.enclosingElement; |
| 151 } |
| 152 if (element.position() == null && !element.isCompilationUnit()) { |
| 153 // Sometimes, the backend fakes up elements that have no |
| 154 // position. So we use the enclosing element instead. It is |
| 155 // not a good error location, but cancel really is "internal |
| 156 // error" or "not implemented yet", so the vicinity is good |
| 157 // enough for now. |
| 158 element = element.enclosingElement; |
| 159 // TODO(ahe): I plan to overhaul this infrastructure anyways. |
| 160 } |
| 161 if (element == null) { |
| 162 element = currentElement; |
| 163 } |
| 164 Token position = element.position(); |
| 165 Uri uri = element.getCompilationUnit().script.uri; |
| 166 return (position == null) |
| 167 ? new SourceSpan(uri, 0, 0) |
| 168 : spanFromTokens(position, position, uri); |
| 169 } |
| 170 |
| 171 SourceSpan spanFromHInstruction(HInstruction instruction) { |
| 172 Element element = instruction.sourceElement; |
| 173 if (element == null) element = currentElement; |
| 174 var position = instruction.sourcePosition; |
| 175 if (position == null) return spanFromElement(element); |
| 176 Token token = position.token; |
| 177 if (token == null) return spanFromElement(element); |
| 178 Uri uri = element.getCompilationUnit().script.uri; |
| 179 return spanFromTokens(token, token, uri); |
| 180 } |
| 181 |
| 182 void log(message) { |
| 183 print(message); |
| 184 } |
| 185 |
| 186 void internalError(String message, |
| 187 {Node node, Token token, HInstruction instruction, |
| 188 Element element}) { |
| 189 cancel('Internal error: $message', node: node, token: token, |
| 190 instruction: instruction, element: element); |
| 191 } |
| 192 |
| 193 void internalErrorOnElement(Element element, String message) { |
| 194 internalError(message, element: element); |
| 195 } |
| 196 |
| 197 // What you say? One kind of "Diagnostic" isn't enough? Well, have two! |
| 198 void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) { |
| 199 reportMessageString(span, "$message", kind); |
| 200 } |
| 201 |
| 202 // Note: renamed this, because otherwise you had "reportDiagnostic" reporting |
| 203 // message strings and "reportMessage" reporting Diagnostics... |
| 204 void reportMessageString(SourceSpan span, String message, |
| 205 api.Diagnostic kind) { |
| 206 |
| 207 // TODO(jmesserly): we should validate that the Uri is what we expected. |
| 208 var msg = message.toString(); |
| 209 var ourSpan = convertToOurSpan(span); |
| 210 var file = new fs.Path(span.uri.toString()); |
| 211 |
| 212 switch (kind) { |
| 213 case api.Diagnostic.ERROR: |
| 214 case api.Diagnostic.CRASH: |
| 215 messages.error(msg, ourSpan, file: file); |
| 216 return; |
| 217 case api.Diagnostic.WARNING: |
| 218 messages.error(msg, ourSpan, file: file); |
| 219 return; |
| 220 case api.Diagnostic.LINT: |
| 221 case api.Diagnostic.INFO: |
| 222 case api.Diagnostic.VERBOSE_INFO: |
| 223 messages.info(msg, ourSpan, file: file); |
| 224 return; |
| 225 } |
| 226 } |
| 227 |
| 228 bool onDeprecatedFeature(Spannable span, String feature) { |
| 229 // Not our job to warn about deprecated Dart features |
| 230 return false; |
| 231 } |
| 232 |
| 233 dom_parsing.SourceSpan convertToOurSpan(SourceSpan span) { |
| 234 // TODO(jmesserly): make html5lib spans less crazy. |
| 235 return new dom_parsing.SourceSpan(fileInfo, span.begin, span.end); |
| 236 } |
| 237 } |
| 238 |
| 239 |
| 240 // TODO(jmesserly): fix dom_parsing.SourceFileInfo. It should compute line |
| 241 // locations itself on demand and caching. At the very least this function |
| 242 // should move into that class. |
| 243 dom_parsing.SourceFileInfo _createSourceFileInfo(String text) { |
| 244 var lineStarts = <int>[0]; |
| 245 var chars = stringToCodepoints(text); |
| 246 |
| 247 for (int i = 0; i < chars.length; i++) { |
| 248 var c = chars[i]; |
| 249 |
| 250 if (c == $CR) { |
| 251 // Return not followed by newline is treated as a newline |
| 252 int j = i + 1; |
| 253 if (j >= chars.length || chars[j] != $LF) { |
| 254 c = $LF; |
| 255 } |
| 256 } |
| 257 |
| 258 if (c == $LF) lineStarts.add(i + 1); |
| 259 } |
| 260 |
| 261 return new dom_parsing.SourceFileInfo(lineStarts, chars); |
| 262 } |
OLD | NEW |