Index: lib/src/dart_parser.dart |
diff --git a/lib/src/dart_parser.dart b/lib/src/dart_parser.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a6f2c9aad4cb8169156b097389b3515a37ed1af5 |
--- /dev/null |
+++ b/lib/src/dart_parser.dart |
@@ -0,0 +1,262 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+/** |
+ * Parser for Dart code based on the dart2js parser. |
+ * |
+ * Use [DartCodeParser.parse] to parse top-level code, and the returned |
+ * instance to parse additonal structures such as classes. |
+ */ |
+library dart_parser; |
+ |
+import 'dart:uri'; |
+import 'dart:utf'; |
+import 'package:compiler_unsupported/implementation/dart2jslib.dart' as dart2js; |
+import 'package:compiler_unsupported/implementation/elements/elements.dart'; |
+import 'package:compiler_unsupported/implementation/elements/modelx.dart'; |
+import 'package:compiler_unsupported/implementation/scanner/scannerlib.dart'; |
+import 'package:compiler_unsupported/implementation/source_file.dart'; |
+import 'package:compiler_unsupported/implementation/tree/tree.dart'; |
+import 'package:compiler_unsupported/implementation/util/util.dart'; |
+import 'package:compiler_unsupported/implementation/util/characters.dart'; |
+import 'package:compiler_unsupported/compiler.dart' as api; |
+import 'package:html5lib/dom_parsing.dart' as dom_parsing; |
+import 'messages.dart' show Messages; |
+import 'file_system/path.dart' as fs; |
+ |
+// TODO(jmesserly): reconcile this with DartCodeInfo |
+class DartCodeParser { |
+ final DiagnosticListener diagnostics; |
+ final CompilationUnitElement unit; |
+ final String code; |
+ bool _success; |
+ |
+ bool get success => _success; |
+ |
+ Messages get messages => diagnostics.messages; |
+ |
+ DartCodeParser._parse(this.diagnostics, this.unit, this.code) { |
+ _success = _parseCompilationUnit(); |
+ } |
+ |
+ /** |
+ * Performs a partial parse of Dart code and returns the parser. |
+ * From their you can determine if the parse was a [success] and get the |
+ * compilation [unit]. |
+ * |
+ * The partial parse only parses the top-level structure. Futher parsing can |
+ * then be performed using the parser, such as [parseClass]. |
+ * |
+ * You can provide a object to receive warning and error [messages] from the |
+ * parser, otherwise [this.messages] will be a new messages instance that is |
+ * silent (does not print). |
+ */ |
+ factory DartCodeParser.parse(String path, String code, {Messages messages}) { |
+ if (messages == null) messages = new Messages.silent(); |
+ var uri = new Uri.fromComponents(path: path); |
+ var script = new dart2js.Script(uri, new SourceFile(uri.toString(), code)); |
+ var unit = new LibraryElementX(script, uri).entryCompilationUnit; |
+ var fileSpanInfo = _createSourceFileInfo(code); |
+ var diagnostics = new DiagnosticListener(fileSpanInfo, unit, messages); |
+ return new DartCodeParser._parse(diagnostics, unit, code); |
+ } |
+ |
+ bool _parseCompilationUnit() { |
+ int nextFreeClassId = 0; |
+ var idGenerator = () => nextFreeClassId++; |
+ var listener = new ElementListener(diagnostics, unit, idGenerator); |
+ |
+ // Try parsing the code. Note that the parser can throw an exception to bail |
+ // out of the call stack. |
+ try { |
+ var tokens = new StringScanner(code).tokenize(); |
+ new PartialParser(listener).parseUnit(tokens); |
+ return true; |
+ } on dart2js.CompilerCancelledException catch (e) { |
+ return false; |
+ } |
+ } |
+ |
+ ClassNode parseClass(PartialClassElement element) { |
+ if (element.cachedNode != null) return element.cachedNode; |
+ var listener = new MemberListener(diagnostics, element); |
+ var parser = new ClassElementParser(listener); |
+ var token = parser.parseTopLevelDeclaration(element.beginToken); |
+ assert(identical(token, element.endToken.next)); |
+ var classNode = listener.popNode(); |
+ assert(listener.nodes.isEmpty); |
+ return element.cachedNode = classNode; |
+ } |
+} |
+ |
+ |
+/** |
+ * DiagnosticListener for dart2js. Figures out warning/error spans, and adapts |
+ * them to the [Messages] format used in web_ui. |
+ * |
+ * This class is madness. It has to deal with spans/messages/paths/uris from |
+ * 3+ compilers colliding (for those keeping score: dart2js, frog remnants |
+ * inside dart2js, html5lib, and web_ui ...). Also a lot of this code had to be |
+ * copied from the dart2js Compiler class to make DiagnosticListener actually |
+ * work. |
+ * |
+ * Avert your eyes! |
+ */ |
+class DiagnosticListener implements dart2js.DiagnosticListener { |
+ final dom_parsing.SourceFileInfo fileInfo; |
+ final CompilationUnitElement currentElement; |
+ final Messages messages; |
+ |
+ DiagnosticListener(this.fileInfo, this.currentElement, this.messages); |
+ |
+ void cancel(String reason, {node, token, instruction, element}) { |
+ SourceSpan span = null; |
+ if (node != null) { |
+ span = spanFromNode(node); |
+ } else if (token != null) { |
+ span = spanFromTokens(token, token); |
+ } else if (instruction != null) { |
+ span = spanFromHInstruction(instruction); |
+ } else if (element != null) { |
+ span = spanFromElement(element); |
+ } else { |
+ throw 'No error location for error: $reason'; |
+ } |
+ reportMessageString(span, reason, api.Diagnostic.ERROR); |
+ throw new CompilerCancelledException(reason); |
+ } |
+ |
+ SourceSpan spanFromTokens(Token begin, Token end, [Uri uri]) { |
+ if (begin == null || end == null) { |
+ // TODO(ahe): We can almost always do better. Often it is only |
+ // end that is null. Otherwise, we probably know the current |
+ // URI. |
+ throw 'Cannot find tokens to produce error message.'; |
+ } |
+ if (uri == null && currentElement != null) { |
+ uri = currentElement.getCompilationUnit().script.uri; |
+ } |
+ return SourceSpan.withCharacterOffsets(begin, end, |
+ (beginOffset, endOffset) => new SourceSpan(uri, beginOffset, endOffset)); |
+ } |
+ |
+ SourceSpan spanFromNode(Node node, [Uri uri]) { |
+ return spanFromTokens(node.getBeginToken(), node.getEndToken(), uri); |
+ } |
+ |
+ SourceSpan spanFromElement(Element element) { |
+ if (Elements.isErroneousElement(element)) { |
+ element = element.enclosingElement; |
+ } |
+ if (element.position() == null && !element.isCompilationUnit()) { |
+ // Sometimes, the backend fakes up elements that have no |
+ // position. So we use the enclosing element instead. It is |
+ // not a good error location, but cancel really is "internal |
+ // error" or "not implemented yet", so the vicinity is good |
+ // enough for now. |
+ element = element.enclosingElement; |
+ // TODO(ahe): I plan to overhaul this infrastructure anyways. |
+ } |
+ if (element == null) { |
+ element = currentElement; |
+ } |
+ Token position = element.position(); |
+ Uri uri = element.getCompilationUnit().script.uri; |
+ return (position == null) |
+ ? new SourceSpan(uri, 0, 0) |
+ : spanFromTokens(position, position, uri); |
+ } |
+ |
+ SourceSpan spanFromHInstruction(HInstruction instruction) { |
+ Element element = instruction.sourceElement; |
+ if (element == null) element = currentElement; |
+ var position = instruction.sourcePosition; |
+ if (position == null) return spanFromElement(element); |
+ Token token = position.token; |
+ if (token == null) return spanFromElement(element); |
+ Uri uri = element.getCompilationUnit().script.uri; |
+ return spanFromTokens(token, token, uri); |
+ } |
+ |
+ void log(message) { |
+ print(message); |
+ } |
+ |
+ void internalError(String message, |
+ {Node node, Token token, HInstruction instruction, |
+ Element element}) { |
+ cancel('Internal error: $message', node: node, token: token, |
+ instruction: instruction, element: element); |
+ } |
+ |
+ void internalErrorOnElement(Element element, String message) { |
+ internalError(message, element: element); |
+ } |
+ |
+ // What you say? One kind of "Diagnostic" isn't enough? Well, have two! |
+ void reportMessage(SourceSpan span, Diagnostic message, api.Diagnostic kind) { |
+ reportMessageString(span, "$message", kind); |
+ } |
+ |
+ // Note: renamed this, because otherwise you had "reportDiagnostic" reporting |
+ // message strings and "reportMessage" reporting Diagnostics... |
+ void reportMessageString(SourceSpan span, String message, |
+ api.Diagnostic kind) { |
+ |
+ // TODO(jmesserly): we should validate that the Uri is what we expected. |
+ var msg = message.toString(); |
+ var ourSpan = convertToOurSpan(span); |
+ var file = new fs.Path(span.uri.toString()); |
+ |
+ switch (kind) { |
+ case api.Diagnostic.ERROR: |
+ case api.Diagnostic.CRASH: |
+ messages.error(msg, ourSpan, file: file); |
+ return; |
+ case api.Diagnostic.WARNING: |
+ messages.error(msg, ourSpan, file: file); |
+ return; |
+ case api.Diagnostic.LINT: |
+ case api.Diagnostic.INFO: |
+ case api.Diagnostic.VERBOSE_INFO: |
+ messages.info(msg, ourSpan, file: file); |
+ return; |
+ } |
+ } |
+ |
+ bool onDeprecatedFeature(Spannable span, String feature) { |
+ // Not our job to warn about deprecated Dart features |
+ return false; |
+ } |
+ |
+ dom_parsing.SourceSpan convertToOurSpan(SourceSpan span) { |
+ // TODO(jmesserly): make html5lib spans less crazy. |
+ return new dom_parsing.SourceSpan(fileInfo, span.begin, span.end); |
+ } |
+} |
+ |
+ |
+// TODO(jmesserly): fix dom_parsing.SourceFileInfo. It should compute line |
+// locations itself on demand and caching. At the very least this function |
+// should move into that class. |
+dom_parsing.SourceFileInfo _createSourceFileInfo(String text) { |
+ var lineStarts = <int>[0]; |
+ var chars = stringToCodepoints(text); |
+ |
+ for (int i = 0; i < chars.length; i++) { |
+ var c = chars[i]; |
+ |
+ if (c == $CR) { |
+ // Return not followed by newline is treated as a newline |
+ int j = i + 1; |
+ if (j >= chars.length || chars[j] != $LF) { |
+ c = $LF; |
+ } |
+ } |
+ |
+ if (c == $LF) lineStarts.add(i + 1); |
+ } |
+ |
+ return new dom_parsing.SourceFileInfo(lineStarts, chars); |
+} |